(function e(t, n, r) {
	console.log(t,n,r);
	function s(o, u) {
		if(!n[o]) {
			if(!t[o]) {
				var a = typeof require == "function" && require;
				if(!u && a) return a(o, !0);
				if(i) return i(o, !0);
				var f = new Error("Cannot find module '" + o + "'");
				throw f.code = "MODULE_NOT_FOUND", f
			}
			var l = n[o] = {
				exports: {}
			};
			t[o][0].call(l.exports, function(e) {
				var n = t[o][1][e];
				return s(n ? n : e)
			}, l, l.exports, e, t, n, r)
		}
		return n[o].exports
	}
	var i = typeof require == "function" && require;
	for(var o = 0; o < r.length; o++) s(r[o]);
	return s
})({
	1: [function(require, module, exports) {
		//     Backbone.js 1.1.2

		//     (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
		//     Backbone may be freely distributed under the MIT license.
		//     For all details and documentation:
		//     http://backbonejs.org

		(function(root, factory) {

			// Set up Backbone appropriately for the environment. Start with AMD.
			if(typeof define === 'function' && define.amd) {
				define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
					// Export global even in AMD case in case this script is loaded with
					// others that may still expect a global Backbone.
					root.Backbone = factory(root, exports, _, $);
				});

				// Next for Node.js or CommonJS. jQuery may not be needed as a module.
			} else if(typeof exports !== 'undefined') {
				var _ = require('underscore');
				factory(root, exports, _);

				// Finally, as a browser global.
			} else {
				root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
			}

		}(this, function(root, Backbone, _, $) {

			// Initial Setup
			// -------------

			// Save the previous value of the `Backbone` variable, so that it can be
			// restored later on, if `noConflict` is used.
			var previousBackbone = root.Backbone;

			// Create local references to array methods we'll want to use later.
			var array = [];
			var push = array.push;
			var slice = array.slice;
			var splice = array.splice;

			// Current version of the library. Keep in sync with `package.json`.
			Backbone.VERSION = '1.1.2';

			// For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
			// the `$` variable.
			Backbone.$ = $;

			// Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
			// to its previous owner. Returns a reference to this Backbone object.
			Backbone.noConflict = function() {
				root.Backbone = previousBackbone;
				return this;
			};

			// Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
			// will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
			// set a `X-Http-Method-Override` header.
			Backbone.emulateHTTP = false;

			// Turn on `emulateJSON` to support legacy servers that can't deal with direct
			// `application/json` requests ... will encode the body as
			// `application/x-www-form-urlencoded` instead and will send the model in a
			// form param named `model`.
			Backbone.emulateJSON = false;

			// Backbone.Events
			// ---------------

			// A module that can be mixed in to *any object* in order to provide it with
			// custom events. You may bind with `on` or remove with `off` callback
			// functions to an event; `trigger`-ing an event fires all callbacks in
			// succession.
			//
			//     var object = {};
			//     _.extend(object, Backbone.Events);
			//     object.on('expand', function(){ alert('expanded'); });
			//     object.trigger('expand');
			//
			var Events = Backbone.Events = {

				// Bind an event to a `callback` function. Passing `"all"` will bind
				// the callback to all events fired.
				on: function(name, callback, context) {
					if(!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
					this._events || (this._events = {});
					var events = this._events[name] || (this._events[name] = []);
					events.push({
						callback: callback,
						context: context,
						ctx: context || this
					});
					return this;
				},

				// Bind an event to only be triggered a single time. After the first time
				// the callback is invoked, it will be removed.
				once: function(name, callback, context) {
					if(!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
					var self = this;
					var once = _.once(function() {
						self.off(name, once);
						callback.apply(this, arguments);
					});
					once._callback = callback;
					return this.on(name, once, context);
				},

				// Remove one or many callbacks. If `context` is null, removes all
				// callbacks with that function. If `callback` is null, removes all
				// callbacks for the event. If `name` is null, removes all bound
				// callbacks for all events.
				off: function(name, callback, context) {
					var retain, ev, events, names, i, l, j, k;
					if(!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
					if(!name && !callback && !context) {
						this._events = void 0;
						return this;
					}
					names = name ? [name] : _.keys(this._events);
					for(i = 0, l = names.length; i < l; i++) {
						name = names[i];
						if(events = this._events[name]) {
							this._events[name] = retain = [];
							if(callback || context) {
								for(j = 0, k = events.length; j < k; j++) {
									ev = events[j];
									if((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
										(context && context !== ev.context)) {
										retain.push(ev);
									}
								}
							}
							if(!retain.length) delete this._events[name];
						}
					}

					return this;
				},

				// Trigger one or many events, firing all bound callbacks. Callbacks are
				// passed the same arguments as `trigger` is, apart from the event name
				// (unless you're listening on `"all"`, which will cause your callback to
				// receive the true name of the event as the first argument).
				trigger: function(name) {
					if(!this._events) return this;
					var args = slice.call(arguments, 1);
					if(!eventsApi(this, 'trigger', name, args)) return this;
					var events = this._events[name];
					var allEvents = this._events.all;
					if(events) triggerEvents(events, args);
					if(allEvents) triggerEvents(allEvents, arguments);
					return this;
				},

				// Tell this object to stop listening to either specific events ... or
				// to every object it's currently listening to.
				stopListening: function(obj, name, callback) {
					var listeningTo = this._listeningTo;
					if(!listeningTo) return this;
					var remove = !name && !callback;
					if(!callback && typeof name === 'object') callback = this;
					if(obj)(listeningTo = {})[obj._listenId] = obj;
					for(var id in listeningTo) {
						obj = listeningTo[id];
						obj.off(name, callback, this);
						if(remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
					}
					return this;
				}

			};

			// Regular expression used to split event strings.
			var eventSplitter = /\s+/;

			// Implement fancy features of the Events API such as multiple event
			// names `"change blur"` and jQuery-style event maps `{change: action}`
			// in terms of the existing API.
			var eventsApi = function(obj, action, name, rest) {
				if(!name) return true;

				// Handle event maps.
				if(typeof name === 'object') {
					for(var key in name) {
						obj[action].apply(obj, [key, name[key]].concat(rest));
					}
					return false;
				}

				// Handle space separated event names.
				if(eventSplitter.test(name)) {
					var names = name.split(eventSplitter);
					for(var i = 0, l = names.length; i < l; i++) {
						obj[action].apply(obj, [names[i]].concat(rest));
					}
					return false;
				}

				return true;
			};

			// A difficult-to-believe, but optimized internal dispatch function for
			// triggering events. Tries to keep the usual cases speedy (most internal
			// Backbone events have 3 arguments).
			var triggerEvents = function(events, args) {
				var ev, i = -1,
					l = events.length,
					a1 = args[0],
					a2 = args[1],
					a3 = args[2];
				switch(args.length) {
					case 0:
						while(++i < l)(ev = events[i]).callback.call(ev.ctx);
						return;
					case 1:
						while(++i < l)(ev = events[i]).callback.call(ev.ctx, a1);
						return;
					case 2:
						while(++i < l)(ev = events[i]).callback.call(ev.ctx, a1, a2);
						return;
					case 3:
						while(++i < l)(ev = events[i]).callback.call(ev.ctx, a1, a2, a3);
						return;
					default:
						while(++i < l)(ev = events[i]).callback.apply(ev.ctx, args);
						return;
				}
			};

			var listenMethods = {
				listenTo: 'on',
				listenToOnce: 'once'
			};

			// Inversion-of-control versions of `on` and `once`. Tell *this* object to
			// listen to an event in another object ... keeping track of what it's
			// listening to.
			_.each(listenMethods, function(implementation, method) {
				Events[method] = function(obj, name, callback) {
					var listeningTo = this._listeningTo || (this._listeningTo = {});
					var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
					listeningTo[id] = obj;
					if(!callback && typeof name === 'object') callback = this;
					obj[implementation](name, callback, this);
					return this;
				};
			});

			// Aliases for backwards compatibility.
			Events.bind = Events.on;
			Events.unbind = Events.off;

			// Allow the `Backbone` object to serve as a global event bus, for folks who
			// want global "pubsub" in a convenient place.
			_.extend(Backbone, Events);

			// Backbone.Model
			// --------------

			// Backbone **Models** are the basic data object in the framework --
			// frequently representing a row in a table in a database on your server.
			// A discrete chunk of data and a bunch of useful, related methods for
			// performing computations and transformations on that data.

			// Create a new model with the specified attributes. A client id (`cid`)
			// is automatically generated and assigned for you.
			var Model = Backbone.Model = function(attributes, options) {
				var attrs = attributes || {};
				options || (options = {});
				this.cid = _.uniqueId('c');
				this.attributes = {};
				if(options.collection) this.collection = options.collection;
				if(options.parse) attrs = this.parse(attrs, options) || {};
				attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
				this.set(attrs, options);
				this.changed = {};
				this.initialize.apply(this, arguments);
			};

			// Attach all inheritable methods to the Model prototype.
			_.extend(Model.prototype, Events, {

				// A hash of attributes whose current and previous value differ.
				changed: null,

				// The value returned during the last failed validation.
				validationError: null,

				// The default name for the JSON `id` attribute is `"id"`. MongoDB and
				// CouchDB users may want to set this to `"_id"`.
				idAttribute: 'id',

				// Initialize is an empty function by default. Override it with your own
				// initialization logic.
				initialize: function() {},

				// Return a copy of the model's `attributes` object.
				toJSON: function(options) {
					return _.clone(this.attributes);
				},

				// Proxy `Backbone.sync` by default -- but override this if you need
				// custom syncing semantics for *this* particular model.
				sync: function() {
					return Backbone.sync.apply(this, arguments);
				},

				// Get the value of an attribute.
				get: function(attr) {
					return this.attributes[attr];
				},

				// Get the HTML-escaped value of an attribute.
				escape: function(attr) {
					return _.escape(this.get(attr));
				},

				// Returns `true` if the attribute contains a value that is not null
				// or undefined.
				has: function(attr) {
					return this.get(attr) != null;
				},

				// Set a hash of model attributes on the object, firing `"change"`. This is
				// the core primitive operation of a model, updating the data and notifying
				// anyone who needs to know about the change in state. The heart of the beast.
				set: function(key, val, options) {
					var attr, attrs, unset, changes, silent, changing, prev, current;
					if(key == null) return this;

					// Handle both `"key", value` and `{key: value}` -style arguments.
					if(typeof key === 'object') {
						attrs = key;
						options = val;
					} else {
						(attrs = {})[key] = val;
					}

					options || (options = {});

					// Run validation.
					if(!this._validate(attrs, options)) return false;

					// Extract attributes and options.
					unset = options.unset;
					silent = options.silent;
					changes = [];
					changing = this._changing;
					this._changing = true;

					if(!changing) {
						this._previousAttributes = _.clone(this.attributes);
						this.changed = {};
					}
					current = this.attributes, prev = this._previousAttributes;

					// Check for changes of `id`.
					if(this.idAttribute in attrs) this.id = attrs[this.idAttribute];

					// For each `set` attribute, update or delete the current value.
					for(attr in attrs) {
						val = attrs[attr];
						if(!_.isEqual(current[attr], val)) changes.push(attr);
						if(!_.isEqual(prev[attr], val)) {
							this.changed[attr] = val;
						} else {
							delete this.changed[attr];
						}
						unset ? delete current[attr] : current[attr] = val;
					}

					// Trigger all relevant attribute changes.
					if(!silent) {
						if(changes.length) this._pending = options;
						for(var i = 0, l = changes.length; i < l; i++) {
							this.trigger('change:' + changes[i], this, current[changes[i]], options);
						}
					}

					// You might be wondering why there's a `while` loop here. Changes can
					// be recursively nested within `"change"` events.
					if(changing) return this;
					if(!silent) {
						while(this._pending) {
							options = this._pending;
							this._pending = false;
							this.trigger('change', this, options);
						}
					}
					this._pending = false;
					this._changing = false;
					return this;
				},

				// Remove an attribute from the model, firing `"change"`. `unset` is a noop
				// if the attribute doesn't exist.
				unset: function(attr, options) {
					return this.set(attr, void 0, _.extend({}, options, {
						unset: true
					}));
				},

				// Clear all attributes on the model, firing `"change"`.
				clear: function(options) {
					var attrs = {};
					for(var key in this.attributes) attrs[key] = void 0;
					return this.set(attrs, _.extend({}, options, {
						unset: true
					}));
				},

				// Determine if the model has changed since the last `"change"` event.
				// If you specify an attribute name, determine if that attribute has changed.
				hasChanged: function(attr) {
					if(attr == null) return !_.isEmpty(this.changed);
					return _.has(this.changed, attr);
				},

				// Return an object containing all the attributes that have changed, or
				// false if there are no changed attributes. Useful for determining what
				// parts of a view need to be updated and/or what attributes need to be
				// persisted to the server. Unset attributes will be set to undefined.
				// You can also pass an attributes object to diff against the model,
				// determining if there *would be* a change.
				changedAttributes: function(diff) {
					if(!diff) return this.hasChanged() ? _.clone(this.changed) : false;
					var val, changed = false;
					var old = this._changing ? this._previousAttributes : this.attributes;
					for(var attr in diff) {
						if(_.isEqual(old[attr], (val = diff[attr]))) continue;
						(changed || (changed = {}))[attr] = val;
					}
					return changed;
				},

				// Get the previous value of an attribute, recorded at the time the last
				// `"change"` event was fired.
				previous: function(attr) {
					if(attr == null || !this._previousAttributes) return null;
					return this._previousAttributes[attr];
				},

				// Get all of the attributes of the model at the time of the previous
				// `"change"` event.
				previousAttributes: function() {
					return _.clone(this._previousAttributes);
				},

				// Fetch the model from the server. If the server's representation of the
				// model differs from its current attributes, they will be overridden,
				// triggering a `"change"` event.
				fetch: function(options) {
					options = options ? _.clone(options) : {};
					if(options.parse === void 0) options.parse = true;
					var model = this;
					var success = options.success;
					options.success = function(resp) {
						if(!model.set(model.parse(resp, options), options)) return false;
						if(success) success(model, resp, options);
						model.trigger('sync', model, resp, options);
					};
					wrapError(this, options);
					return this.sync('read', this, options);
				},

				// Set a hash of model attributes, and sync the model to the server.
				// If the server returns an attributes hash that differs, the model's
				// state will be `set` again.
				save: function(key, val, options) {
					var attrs, method, xhr, attributes = this.attributes;

					// Handle both `"key", value` and `{key: value}` -style arguments.
					if(key == null || typeof key === 'object') {
						attrs = key;
						options = val;
					} else {
						(attrs = {})[key] = val;
					}

					options = _.extend({
						validate: true
					}, options);

					// If we're not waiting and attributes exist, save acts as
					// `set(attr).save(null, opts)` with validation. Otherwise, check if
					// the model will be valid when the attributes, if any, are set.
					if(attrs && !options.wait) {
						if(!this.set(attrs, options)) return false;
					} else {
						if(!this._validate(attrs, options)) return false;
					}

					// Set temporary attributes if `{wait: true}`.
					if(attrs && options.wait) {
						this.attributes = _.extend({}, attributes, attrs);
					}

					// After a successful server-side save, the client is (optionally)
					// updated with the server-side state.
					if(options.parse === void 0) options.parse = true;
					var model = this;
					var success = options.success;
					options.success = function(resp) {
						// Ensure attributes are restored during synchronous saves.
						model.attributes = attributes;
						var serverAttrs = model.parse(resp, options);
						if(options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
						if(_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
							return false;
						}
						if(success) success(model, resp, options);
						model.trigger('sync', model, resp, options);
					};
					wrapError(this, options);

					method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
					if(method === 'patch') options.attrs = attrs;
					xhr = this.sync(method, this, options);

					// Restore attributes.
					if(attrs && options.wait) this.attributes = attributes;

					return xhr;
				},

				// Destroy this model on the server if it was already persisted.
				// Optimistically removes the model from its collection, if it has one.
				// If `wait: true` is passed, waits for the server to respond before removal.
				destroy: function(options) {
					options = options ? _.clone(options) : {};
					var model = this;
					var success = options.success;

					var destroy = function() {
						model.trigger('destroy', model, model.collection, options);
					};

					options.success = function(resp) {
						if(options.wait || model.isNew()) destroy();
						if(success) success(model, resp, options);
						if(!model.isNew()) model.trigger('sync', model, resp, options);
					};

					if(this.isNew()) {
						options.success();
						return false;
					}
					wrapError(this, options);

					var xhr = this.sync('delete', this, options);
					if(!options.wait) destroy();
					return xhr;
				},

				// Default URL for the model's representation on the server -- if you're
				// using Backbone's restful methods, override this to change the endpoint
				// that will be called.
				url: function() {
					var base =
						_.result(this, 'urlRoot') ||
						_.result(this.collection, 'url') ||
						urlError();
					if(this.isNew()) return base;
					return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
				},

				// **parse** converts a response into the hash of attributes to be `set` on
				// the model. The default implementation is just to pass the response along.
				parse: function(resp, options) {
					return resp;
				},

				// Create a new model with identical attributes to this one.
				clone: function() {
					return new this.constructor(this.attributes);
				},

				// A model is new if it has never been saved to the server, and lacks an id.
				isNew: function() {
					return !this.has(this.idAttribute);
				},

				// Check if the model is currently in a valid state.
				isValid: function(options) {
					return this._validate({}, _.extend(options || {}, {
						validate: true
					}));
				},

				// Run validation against the next complete set of model attributes,
				// returning `true` if all is well. Otherwise, fire an `"invalid"` event.
				_validate: function(attrs, options) {
					if(!options.validate || !this.validate) return true;
					attrs = _.extend({}, this.attributes, attrs);
					var error = this.validationError = this.validate(attrs, options) || null;
					if(!error) return true;
					this.trigger('invalid', this, error, _.extend(options, {
						validationError: error
					}));
					return false;
				}

			});

			// Underscore methods that we want to implement on the Model.
			var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];

			// Mix in each Underscore method as a proxy to `Model#attributes`.
			_.each(modelMethods, function(method) {
				Model.prototype[method] = function() {
					var args = slice.call(arguments);
					args.unshift(this.attributes);
					return _[method].apply(_, args);
				};
			});

			// Backbone.Collection
			// -------------------

			// If models tend to represent a single row of data, a Backbone Collection is
			// more analagous to a table full of data ... or a small slice or page of that
			// table, or a collection of rows that belong together for a particular reason
			// -- all of the messages in this particular folder, all of the documents
			// belonging to this particular author, and so on. Collections maintain
			// indexes of their models, both in order, and for lookup by `id`.

			// Create a new **Collection**, perhaps to contain a specific type of `model`.
			// If a `comparator` is specified, the Collection will maintain
			// its models in sort order, as they're added and removed.
			var Collection = Backbone.Collection = function(models, options) {
				options || (options = {});
				if(options.model) this.model = options.model;
				if(options.comparator !== void 0) this.comparator = options.comparator;
				this._reset();
				this.initialize.apply(this, arguments);
				if(models) this.reset(models, _.extend({
					silent: true
				}, options));
			};

			// Default options for `Collection#set`.
			var setOptions = {
				add: true,
				remove: true,
				merge: true
			};
			var addOptions = {
				add: true,
				remove: false
			};

			// Define the Collection's inheritable methods.
			_.extend(Collection.prototype, Events, {

				// The default model for a collection is just a **Backbone.Model**.
				// This should be overridden in most cases.
				model: Model,

				// Initialize is an empty function by default. Override it with your own
				// initialization logic.
				initialize: function() {},

				// The JSON representation of a Collection is an array of the
				// models' attributes.
				toJSON: function(options) {
					return this.map(function(model) {
						return model.toJSON(options);
					});
				},

				// Proxy `Backbone.sync` by default.
				sync: function() {
					return Backbone.sync.apply(this, arguments);
				},

				// Add a model, or list of models to the set.
				add: function(models, options) {
					return this.set(models, _.extend({
						merge: false
					}, options, addOptions));
				},

				// Remove a model, or a list of models from the set.
				remove: function(models, options) {
					var singular = !_.isArray(models);
					models = singular ? [models] : _.clone(models);
					options || (options = {});
					var i, l, index, model;
					for(i = 0, l = models.length; i < l; i++) {
						model = models[i] = this.get(models[i]);
						if(!model) continue;
						delete this._byId[model.id];
						delete this._byId[model.cid];
						index = this.indexOf(model);
						this.models.splice(index, 1);
						this.length--;
						if(!options.silent) {
							options.index = index;
							model.trigger('remove', model, this, options);
						}
						this._removeReference(model, options);
					}
					return singular ? models[0] : models;
				},

				// Update a collection by `set`-ing a new list of models, adding new ones,
				// removing models that are no longer present, and merging models that
				// already exist in the collection, as necessary. Similar to **Model#set**,
				// the core operation for updating the data contained by the collection.
				set: function(models, options) {
					options = _.defaults({}, options, setOptions);
					if(options.parse) models = this.parse(models, options);
					var singular = !_.isArray(models);
					models = singular ? (models ? [models] : []) : _.clone(models);
					var i, l, id, model, attrs, existing, sort;
					var at = options.at;
					var targetModel = this.model;
					var sortable = this.comparator && (at == null) && options.sort !== false;
					var sortAttr = _.isString(this.comparator) ? this.comparator : null;
					var toAdd = [],
						toRemove = [],
						modelMap = {};
					var add = options.add,
						merge = options.merge,
						remove = options.remove;
					var order = !sortable && add && remove ? [] : false;

					// Turn bare objects into model references, and prevent invalid models
					// from being added.
					for(i = 0, l = models.length; i < l; i++) {
						attrs = models[i] || {};
						if(attrs instanceof Model) {
							id = model = attrs;
						} else {
							id = attrs[targetModel.prototype.idAttribute || 'id'];
						}

						// If a duplicate is found, prevent it from being added and
						// optionally merge it into the existing model.
						if(existing = this.get(id)) {
							if(remove) modelMap[existing.cid] = true;
							if(merge) {
								attrs = attrs === model ? model.attributes : attrs;
								if(options.parse) attrs = existing.parse(attrs, options);
								existing.set(attrs, options);
								if(sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
							}
							models[i] = existing;

							// If this is a new, valid model, push it to the `toAdd` list.
						} else if(add) {
							model = models[i] = this._prepareModel(attrs, options);
							if(!model) continue;
							toAdd.push(model);
							this._addReference(model, options);
						}

						// Do not add multiple models with the same `id`.
						model = existing || model;
						if(order && (model.isNew() || !modelMap[model.id])) order.push(model);
						modelMap[model.id] = true;
					}

					// Remove nonexistent models if appropriate.
					if(remove) {
						for(i = 0, l = this.length; i < l; ++i) {
							if(!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
						}
						if(toRemove.length) this.remove(toRemove, options);
					}

					// See if sorting is needed, update `length` and splice in new models.
					if(toAdd.length || (order && order.length)) {
						if(sortable) sort = true;
						this.length += toAdd.length;
						if(at != null) {
							for(i = 0, l = toAdd.length; i < l; i++) {
								this.models.splice(at + i, 0, toAdd[i]);
							}
						} else {
							if(order) this.models.length = 0;
							var orderedModels = order || toAdd;
							for(i = 0, l = orderedModels.length; i < l; i++) {
								this.models.push(orderedModels[i]);
							}
						}
					}

					// Silently sort the collection if appropriate.
					if(sort) this.sort({
						silent: true
					});

					// Unless silenced, it's time to fire all appropriate add/sort events.
					if(!options.silent) {
						for(i = 0, l = toAdd.length; i < l; i++) {
							(model = toAdd[i]).trigger('add', model, this, options);
						}
						if(sort || (order && order.length)) this.trigger('sort', this, options);
					}

					// Return the added (or merged) model (or models).
					return singular ? models[0] : models;
				},

				// When you have more items than you want to add or remove individually,
				// you can reset the entire set with a new list of models, without firing
				// any granular `add` or `remove` events. Fires `reset` when finished.
				// Useful for bulk operations and optimizations.
				reset: function(models, options) {
					options || (options = {});
					for(var i = 0, l = this.models.length; i < l; i++) {
						this._removeReference(this.models[i], options);
					}
					options.previousModels = this.models;
					this._reset();
					models = this.add(models, _.extend({
						silent: true
					}, options));
					if(!options.silent) this.trigger('reset', this, options);
					return models;
				},

				// Add a model to the end of the collection.
				push: function(model, options) {
					return this.add(model, _.extend({
						at: this.length
					}, options));
				},

				// Remove a model from the end of the collection.
				pop: function(options) {
					var model = this.at(this.length - 1);
					this.remove(model, options);
					return model;
				},

				// Add a model to the beginning of the collection.
				unshift: function(model, options) {
					return this.add(model, _.extend({
						at: 0
					}, options));
				},

				// Remove a model from the beginning of the collection.
				shift: function(options) {
					var model = this.at(0);
					this.remove(model, options);
					return model;
				},

				// Slice out a sub-array of models from the collection.
				slice: function() {
					return slice.apply(this.models, arguments);
				},

				// Get a model from the set by id.
				get: function(obj) {
					if(obj == null) return void 0;
					return this._byId[obj] || this._byId[obj.id] || this._byId[obj.cid];
				},

				// Get the model at the given index.
				at: function(index) {
					return this.models[index];
				},

				// Return models with matching attributes. Useful for simple cases of
				// `filter`.
				where: function(attrs, first) {
					if(_.isEmpty(attrs)) return first ? void 0 : [];
					return this[first ? 'find' : 'filter'](function(model) {
						for(var key in attrs) {
							if(attrs[key] !== model.get(key)) return false;
						}
						return true;
					});
				},

				// Return the first model with matching attributes. Useful for simple cases
				// of `find`.
				findWhere: function(attrs) {
					return this.where(attrs, true);
				},

				// Force the collection to re-sort itself. You don't need to call this under
				// normal circumstances, as the set will maintain sort order as each item
				// is added.
				sort: function(options) {
					if(!this.comparator) throw new Error('Cannot sort a set without a comparator');
					options || (options = {});

					// Run sort based on type of `comparator`.
					if(_.isString(this.comparator) || this.comparator.length === 1) {
						this.models = this.sortBy(this.comparator, this);
					} else {
						this.models.sort(_.bind(this.comparator, this));
					}

					if(!options.silent) this.trigger('sort', this, options);
					return this;
				},

				// Pluck an attribute from each model in the collection.
				pluck: function(attr) {
					return _.invoke(this.models, 'get', attr);
				},

				// Fetch the default set of models for this collection, resetting the
				// collection when they arrive. If `reset: true` is passed, the response
				// data will be passed through the `reset` method instead of `set`.
				fetch: function(options) {
					options = options ? _.clone(options) : {};
					if(options.parse === void 0) options.parse = true;
					var success = options.success;
					var collection = this;
					options.success = function(resp) {
						var method = options.reset ? 'reset' : 'set';
						collection[method](resp, options);
						if(success) success(collection, resp, options);
						collection.trigger('sync', collection, resp, options);
					};
					wrapError(this, options);
					return this.sync('read', this, options);
				},

				// Create a new instance of a model in this collection. Add the model to the
				// collection immediately, unless `wait: true` is passed, in which case we
				// wait for the server to agree.
				create: function(model, options) {
					options = options ? _.clone(options) : {};
					if(!(model = this._prepareModel(model, options))) return false;
					if(!options.wait) this.add(model, options);
					var collection = this;
					var success = options.success;
					options.success = function(model, resp) {
						if(options.wait) collection.add(model, options);
						if(success) success(model, resp, options);
					};
					model.save(null, options);
					return model;
				},

				// **parse** converts a response into a list of models to be added to the
				// collection. The default implementation is just to pass it through.
				parse: function(resp, options) {
					return resp;
				},

				// Create a new collection with an identical list of models as this one.
				clone: function() {
					return new this.constructor(this.models);
				},

				// Private method to reset all internal state. Called when the collection
				// is first initialized or reset.
				_reset: function() {
					this.length = 0;
					this.models = [];
					this._byId = {};
				},

				// Prepare a hash of attributes (or other model) to be added to this
				// collection.
				_prepareModel: function(attrs, options) {
					if(attrs instanceof Model) return attrs;
					options = options ? _.clone(options) : {};
					options.collection = this;
					var model = new this.model(attrs, options);
					if(!model.validationError) return model;
					this.trigger('invalid', this, model.validationError, options);
					return false;
				},

				// Internal method to create a model's ties to a collection.
				_addReference: function(model, options) {
					this._byId[model.cid] = model;
					if(model.id != null) this._byId[model.id] = model;
					if(!model.collection) model.collection = this;
					model.on('all', this._onModelEvent, this);
				},

				// Internal method to sever a model's ties to a collection.
				_removeReference: function(model, options) {
					if(this === model.collection) delete model.collection;
					model.off('all', this._onModelEvent, this);
				},

				// Internal method called every time a model in the set fires an event.
				// Sets need to update their indexes when models change ids. All other
				// events simply proxy through. "add" and "remove" events that originate
				// in other collections are ignored.
				_onModelEvent: function(event, model, collection, options) {
					if((event === 'add' || event === 'remove') && collection !== this) return;
					if(event === 'destroy') this.remove(model, options);
					if(model && event === 'change:' + model.idAttribute) {
						delete this._byId[model.previous(model.idAttribute)];
						if(model.id != null) this._byId[model.id] = model;
					}
					this.trigger.apply(this, arguments);
				}

			});

			// Underscore methods that we want to implement on the Collection.
			// 90% of the core usefulness of Backbone Collections is actually implemented
			// right here:
			var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
				'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
				'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
				'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
				'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
				'lastIndexOf', 'isEmpty', 'chain', 'sample'
			];

			// Mix in each Underscore method as a proxy to `Collection#models`.
			_.each(methods, function(method) {
				Collection.prototype[method] = function() {
					var args = slice.call(arguments);
					args.unshift(this.models);
					return _[method].apply(_, args);
				};
			});

			// Underscore methods that take a property name as an argument.
			var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];

			// Use attributes instead of properties.
			_.each(attributeMethods, function(method) {
				Collection.prototype[method] = function(value, context) {
					var iterator = _.isFunction(value) ? value : function(model) {
						return model.get(value);
					};
					return _[method](this.models, iterator, context);
				};
			});

			// Backbone.View
			// -------------

			// Backbone Views are almost more convention than they are actual code. A View
			// is simply a JavaScript object that represents a logical chunk of UI in the
			// DOM. This might be a single item, an entire list, a sidebar or panel, or
			// even the surrounding frame which wraps your whole app. Defining a chunk of
			// UI as a **View** allows you to define your DOM events declaratively, without
			// having to worry about render order ... and makes it easy for the view to
			// react to specific changes in the state of your models.

			// Creating a Backbone.View creates its initial element outside of the DOM,
			// if an existing element is not provided...
			var View = Backbone.View = function(options) {
				this.cid = _.uniqueId('view');
				options || (options = {});
				_.extend(this, _.pick(options, viewOptions));
				this._ensureElement();
				this.initialize.apply(this, arguments);
				this.delegateEvents();
			};

			// Cached regex to split keys for `delegate`.
			var delegateEventSplitter = /^(\S+)\s*(.*)$/;

			// List of view options to be merged as properties.
			var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];

			// Set up all inheritable **Backbone.View** properties and methods.
			_.extend(View.prototype, Events, {

				// The default `tagName` of a View's element is `"div"`.
				tagName: 'div',

				// jQuery delegate for element lookup, scoped to DOM elements within the
				// current view. This should be preferred to global lookups where possible.
				$: function(selector) {
					return this.$el.find(selector);
				},

				// Initialize is an empty function by default. Override it with your own
				// initialization logic.
				initialize: function() {},

				// **render** is the core function that your view should override, in order
				// to populate its element (`this.el`), with the appropriate HTML. The
				// convention is for **render** to always return `this`.
				render: function() {
					return this;
				},

				// Remove this view by taking the element out of the DOM, and removing any
				// applicable Backbone.Events listeners.
				remove: function() {
					this.$el.remove();
					this.stopListening();
					return this;
				},

				// Change the view's element (`this.el` property), including event
				// re-delegation.
				setElement: function(element, delegate) {
					if(this.$el) this.undelegateEvents();
					this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
					this.el = this.$el[0];
					if(delegate !== false) this.delegateEvents();
					return this;
				},

				// Set callbacks, where `this.events` is a hash of
				//
				// *{"event selector": "callback"}*
				//
				//     {
				//       'mousedown .title':  'edit',
				//       'click .button':     'save',
				//       'click .open':       function(e) { ... }
				//     }
				//
				// pairs. Callbacks will be bound to the view, with `this` set properly.
				// Uses event delegation for efficiency.
				// Omitting the selector binds the event to `this.el`.
				// This only works for delegate-able events: not `focus`, `blur`, and
				// not `change`, `submit`, and `reset` in Internet Explorer.
				delegateEvents: function(events) {
					if(!(events || (events = _.result(this, 'events')))) return this;
					this.undelegateEvents();
					for(var key in events) {
						var method = events[key];
						if(!_.isFunction(method)) method = this[events[key]];
						if(!method) continue;

						var match = key.match(delegateEventSplitter);
						var eventName = match[1],
							selector = match[2];
						method = _.bind(method, this);
						eventName += '.delegateEvents' + this.cid;
						if(selector === '') {
							this.$el.on(eventName, method);
						} else {
							this.$el.on(eventName, selector, method);
						}
					}
					return this;
				},

				// Clears all callbacks previously bound to the view with `delegateEvents`.
				// You usually don't need to use this, but may wish to if you have multiple
				// Backbone views attached to the same DOM element.
				undelegateEvents: function() {
					this.$el.off('.delegateEvents' + this.cid);
					return this;
				},

				// Ensure that the View has a DOM element to render into.
				// If `this.el` is a string, pass it through `$()`, take the first
				// matching element, and re-assign it to `el`. Otherwise, create
				// an element from the `id`, `className` and `tagName` properties.
				_ensureElement: function() {
					if(!this.el) {
						var attrs = _.extend({}, _.result(this, 'attributes'));
						if(this.id) attrs.id = _.result(this, 'id');
						if(this.className) attrs['class'] = _.result(this, 'className');
						var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
						this.setElement($el, false);
					} else {
						this.setElement(_.result(this, 'el'), false);
					}
				}

			});

			// Backbone.sync
			// -------------

			// Override this function to change the manner in which Backbone persists
			// models to the server. You will be passed the type of request, and the
			// model in question. By default, makes a RESTful Ajax request
			// to the model's `url()`. Some possible customizations could be:
			//
			// * Use `setTimeout` to batch rapid-fire updates into a single request.
			// * Send up the models as XML instead of JSON.
			// * Persist models via WebSockets instead of Ajax.
			//
			// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
			// as `POST`, with a `_method` parameter containing the true HTTP method,
			// as well as all requests with the body as `application/x-www-form-urlencoded`
			// instead of `application/json` with the model in a param named `model`.
			// Useful when interfacing with server-side languages like **PHP** that make
			// it difficult to read the body of `PUT` requests.
			Backbone.sync = function(method, model, options) {
				var type = methodMap[method];

				// Default options, unless specified.
				_.defaults(options || (options = {}), {
					emulateHTTP: Backbone.emulateHTTP,
					emulateJSON: Backbone.emulateJSON
				});

				// Default JSON-request options.
				var params = {
					type: type,
					dataType: 'json'
				};

				// Ensure that we have a URL.
				if(!options.url) {
					params.url = _.result(model, 'url') || urlError();
				}

				// Ensure that we have the appropriate request data.
				if(options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
					params.contentType = 'application/json';
					params.data = JSON.stringify(options.attrs || model.toJSON(options));
				}

				// For older servers, emulate JSON by encoding the request into an HTML-form.
				if(options.emulateJSON) {
					params.contentType = 'application/x-www-form-urlencoded';
					params.data = params.data ? {
						model: params.data
					} : {};
				}

				// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
				// And an `X-HTTP-Method-Override` header.
				if(options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
					params.type = 'POST';
					if(options.emulateJSON) params.data._method = type;
					var beforeSend = options.beforeSend;
					options.beforeSend = function(xhr) {
						xhr.setRequestHeader('X-HTTP-Method-Override', type);
						if(beforeSend) return beforeSend.apply(this, arguments);
					};
				}

				// Don't process data on a non-GET request.
				if(params.type !== 'GET' && !options.emulateJSON) {
					params.processData = false;
				}

				// If we're sending a `PATCH` request, and we're in an old Internet Explorer
				// that still has ActiveX enabled by default, override jQuery to use that
				// for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
				if(params.type === 'PATCH' && noXhrPatch) {
					params.xhr = function() {
						return new ActiveXObject("Microsoft.XMLHTTP");
					};
				}

				// Make the request, allowing the user to override any Ajax options.
				var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
				model.trigger('request', model, xhr, options);
				return xhr;
			};

			var noXhrPatch =
				typeof window !== 'undefined' && !!window.ActiveXObject &&
				!(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);

			// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
			var methodMap = {
				'create': 'POST',
				'update': 'PUT',
				'patch': 'PATCH',
				'delete': 'DELETE',
				'read': 'GET'
			};

			// Set the default implementation of `Backbone.ajax` to proxy through to `$`.
			// Override this if you'd like to use a different library.
			Backbone.ajax = function() {
				return Backbone.$.ajax.apply(Backbone.$, arguments);
			};

			// Backbone.Router
			// ---------------

			// Routers map faux-URLs to actions, and fire events when routes are
			// matched. Creating a new one sets its `routes` hash, if not set statically.
			var Router = Backbone.Router = function(options) {
				options || (options = {});
				if(options.routes) this.routes = options.routes;
				this._bindRoutes();
				this.initialize.apply(this, arguments);
			};

			// Cached regular expressions for matching named param parts and splatted
			// parts of route strings.
			var optionalParam = /\((.*?)\)/g;
			var namedParam = /(\(\?)?:\w+/g;
			var splatParam = /\*\w+/g;
			var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;

			// Set up all inheritable **Backbone.Router** properties and methods.
			_.extend(Router.prototype, Events, {

				// Initialize is an empty function by default. Override it with your own
				// initialization logic.
				initialize: function() {},

				// Manually bind a single named route to a callback. For example:
				//
				//     this.route('search/:query/p:num', 'search', function(query, num) {
				//       ...
				//     });
				//
				route: function(route, name, callback) {
					if(!_.isRegExp(route)) route = this._routeToRegExp(route);
					if(_.isFunction(name)) {
						callback = name;
						name = '';
					}
					if(!callback) callback = this[name];
					var router = this;
					Backbone.history.route(route, function(fragment) {
						var args = router._extractParameters(route, fragment);
						router.execute(callback, args);
						router.trigger.apply(router, ['route:' + name].concat(args));
						router.trigger('route', name, args);
						Backbone.history.trigger('route', router, name, args);
					});
					return this;
				},

				// Execute a route handler with the provided parameters.  This is an
				// excellent place to do pre-route setup or post-route cleanup.
				execute: function(callback, args) {
					if(callback) callback.apply(this, args);
				},

				// Simple proxy to `Backbone.history` to save a fragment into the history.
				navigate: function(fragment, options) {
					Backbone.history.navigate(fragment, options);
					return this;
				},

				// Bind all defined routes to `Backbone.history`. We have to reverse the
				// order of the routes here to support behavior where the most general
				// routes can be defined at the bottom of the route map.
				_bindRoutes: function() {
					if(!this.routes) return;
					this.routes = _.result(this, 'routes');
					var route, routes = _.keys(this.routes);
					while((route = routes.pop()) != null) {
						this.route(route, this.routes[route]);
					}
				},

				// Convert a route string into a regular expression, suitable for matching
				// against the current location hash.
				_routeToRegExp: function(route) {
					route = route.replace(escapeRegExp, '\\$&')
						.replace(optionalParam, '(?:$1)?')
						.replace(namedParam, function(match, optional) {
							return optional ? match : '([^/?]+)';
						})
						.replace(splatParam, '([^?]*?)');
					return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
				},

				// Given a route, and a URL fragment that it matches, return the array of
				// extracted decoded parameters. Empty or unmatched parameters will be
				// treated as `null` to normalize cross-browser behavior.
				_extractParameters: function(route, fragment) {
					var params = route.exec(fragment).slice(1);
					return _.map(params, function(param, i) {
						// Don't decode the search params.
						if(i === params.length - 1) return param || null;
						return param ? decodeURIComponent(param) : null;
					});
				}

			});

			// Backbone.History
			// ----------------

			// Handles cross-browser history management, based on either
			// [pushState](http://diveintohtml5.info/history.html) and real URLs, or
			// [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
			// and URL fragments. If the browser supports neither (old IE, natch),
			// falls back to polling.
			var History = Backbone.History = function() {
				this.handlers = [];
				_.bindAll(this, 'checkUrl');

				// Ensure that `History` can be used outside of the browser.
				if(typeof window !== 'undefined') {
					this.location = window.location;
					this.history = window.history;
				}
			};

			// Cached regex for stripping a leading hash/slash and trailing space.
			var routeStripper = /^[#\/]|\s+$/g;

			// Cached regex for stripping leading and trailing slashes.
			var rootStripper = /^\/+|\/+$/g;

			// Cached regex for detecting MSIE.
			var isExplorer = /msie [\w.]+/;

			// Cached regex for removing a trailing slash.
			var trailingSlash = /\/$/;

			// Cached regex for stripping urls of hash.
			var pathStripper = /#.*$/;

			// Has the history handling already been started?
			History.started = false;

			// Set up all inheritable **Backbone.History** properties and methods.
			_.extend(History.prototype, Events, {

				// The default interval to poll for hash changes, if necessary, is
				// twenty times a second.
				interval: 50,

				// Are we at the app root?
				atRoot: function() {
					return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root;
				},

				// Gets the true hash value. Cannot use location.hash directly due to bug
				// in Firefox where location.hash will always be decoded.
				getHash: function(window) {
					var match = (window || this).location.href.match(/#(.*)$/);
					return match ? match[1] : '';
				},

				// Get the cross-browser normalized URL fragment, either from the URL,
				// the hash, or the override.
				getFragment: function(fragment, forcePushState) {
					if(fragment == null) {
						if(this._hasPushState || !this._wantsHashChange || forcePushState) {
							fragment = decodeURI(this.location.pathname + this.location.search);
							var root = this.root.replace(trailingSlash, '');
							if(!fragment.indexOf(root)) fragment = fragment.slice(root.length);
						} else {
							fragment = this.getHash();
						}
					}
					return fragment.replace(routeStripper, '');
				},

				// Start the hash change handling, returning `true` if the current URL matches
				// an existing route, and `false` otherwise.
				start: function(options) {
					if(History.started) throw new Error("Backbone.history has already been started");
					History.started = true;

					// Figure out the initial configuration. Do we need an iframe?
					// Is pushState desired ... is it available?
					this.options = _.extend({
						root: '/'
					}, this.options, options);
					this.root = this.options.root;
					this._wantsHashChange = this.options.hashChange !== false;
					this._wantsPushState = !!this.options.pushState;
					this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
					var fragment = this.getFragment();
					var docMode = document.documentMode;
					var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));

					// Normalize root to always include a leading and trailing slash.
					this.root = ('/' + this.root + '/').replace(rootStripper, '/');

					if(oldIE && this._wantsHashChange) {
						var frame = Backbone.$('<iframe src="javascript:0" tabindex="-1">');
						this.iframe = frame.hide().appendTo('body')[0].contentWindow;
						this.navigate(fragment);
					}

					// Depending on whether we're using pushState or hashes, and whether
					// 'onhashchange' is supported, determine how we check the URL state.
					if(this._hasPushState) {
						Backbone.$(window).on('popstate', this.checkUrl);
					} else if(this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
						Backbone.$(window).on('hashchange', this.checkUrl);
					} else if(this._wantsHashChange) {
						this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
					}

					// Determine if we need to change the base url, for a pushState link
					// opened by a non-pushState browser.
					this.fragment = fragment;
					var loc = this.location;

					// Transition from hashChange to pushState or vice versa if both are
					// requested.
					if(this._wantsHashChange && this._wantsPushState) {

						// If we've started off with a route from a `pushState`-enabled
						// browser, but we're currently in a browser that doesn't support it...
						if(!this._hasPushState && !this.atRoot()) {
							this.fragment = this.getFragment(null, true);
							this.location.replace(this.root + '#' + this.fragment);
							// Return immediately as browser will do redirect to new url
							return true;

							// Or if we've started out with a hash-based route, but we're currently
							// in a browser where it could be `pushState`-based instead...
						} else if(this._hasPushState && this.atRoot() && loc.hash) {
							this.fragment = this.getHash().replace(routeStripper, '');
							this.history.replaceState({}, document.title, this.root + this.fragment);
						}

					}

					if(!this.options.silent) return this.loadUrl();
				},

				// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
				// but possibly useful for unit testing Routers.
				stop: function() {
					Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
					if(this._checkUrlInterval) clearInterval(this._checkUrlInterval);
					History.started = false;
				},

				// Add a route to be tested when the fragment changes. Routes added later
				// may override previous routes.
				route: function(route, callback) {
					this.handlers.unshift({
						route: route,
						callback: callback
					});
				},

				// Checks the current URL to see if it has changed, and if it has,
				// calls `loadUrl`, normalizing across the hidden iframe.
				checkUrl: function(e) {
					var current = this.getFragment();
					if(current === this.fragment && this.iframe) {
						current = this.getFragment(this.getHash(this.iframe));
					}
					if(current === this.fragment) return false;
					if(this.iframe) this.navigate(current);
					this.loadUrl();
				},

				// Attempt to load the current URL fragment. If a route succeeds with a
				// match, returns `true`. If no defined routes matches the fragment,
				// returns `false`.
				loadUrl: function(fragment) {
					fragment = this.fragment = this.getFragment(fragment);
					return _.any(this.handlers, function(handler) {
						if(handler.route.test(fragment)) {
							handler.callback(fragment);
							return true;
						}
					});
				},

				// Save a fragment into the hash history, or replace the URL state if the
				// 'replace' option is passed. You are responsible for properly URL-encoding
				// the fragment in advance.
				//
				// The options object can contain `trigger: true` if you wish to have the
				// route callback be fired (not usually desirable), or `replace: true`, if
				// you wish to modify the current URL without adding an entry to the history.
				navigate: function(fragment, options) {
					if(!History.started) return false;
					if(!options || options === true) options = {
						trigger: !!options
					};

					var url = this.root + (fragment = this.getFragment(fragment || ''));

					// Strip the hash for matching.
					fragment = fragment.replace(pathStripper, '');

					if(this.fragment === fragment) return;
					this.fragment = fragment;

					// Don't include a trailing slash on the root.
					if(fragment === '' && url !== '/') url = url.slice(0, -1);

					// If pushState is available, we use it to set the fragment as a real URL.
					if(this._hasPushState) {
						this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);

						// If hash changes haven't been explicitly disabled, update the hash
						// fragment to store history.
					} else if(this._wantsHashChange) {
						this._updateHash(this.location, fragment, options.replace);
						if(this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
							// Opening and closing the iframe tricks IE7 and earlier to push a
							// history entry on hash-tag change.  When replace is true, we don't
							// want this.
							if(!options.replace) this.iframe.document.open().close();
							this._updateHash(this.iframe.location, fragment, options.replace);
						}

						// If you've told us that you explicitly don't want fallback hashchange-
						// based history, then `navigate` becomes a page refresh.
					} else {
						return this.location.assign(url);
					}
					if(options.trigger) return this.loadUrl(fragment);
				},

				// Update the hash location, either replacing the current entry, or adding
				// a new one to the browser history.
				_updateHash: function(location, fragment, replace) {
					if(replace) {
						var href = location.href.replace(/(javascript:|#).*$/, '');
						location.replace(href + '#' + fragment);
					} else {
						// Some browsers require that `hash` contains a leading #.
						location.hash = '#' + fragment;
					}
				}

			});

			// Create the default Backbone.history.
			Backbone.history = new History;

			// Helpers
			// -------

			// Helper function to correctly set up the prototype chain, for subclasses.
			// Similar to `goog.inherits`, but uses a hash of prototype properties and
			// class properties to be extended.
			var extend = function(protoProps, staticProps) {
				var parent = this;
				var child;

				// The constructor function for the new subclass is either defined by you
				// (the "constructor" property in your `extend` definition), or defaulted
				// by us to simply call the parent's constructor.
				if(protoProps && _.has(protoProps, 'constructor')) {
					child = protoProps.constructor;
				} else {
					child = function() {
						return parent.apply(this, arguments);
					};
				}

				// Add static properties to the constructor function, if supplied.
				_.extend(child, parent, staticProps);

				// Set the prototype chain to inherit from `parent`, without calling
				// `parent`'s constructor function.
				var Surrogate = function() {
					this.constructor = child;
				};
				Surrogate.prototype = parent.prototype;
				child.prototype = new Surrogate;

				// Add prototype properties (instance properties) to the subclass,
				// if supplied.
				if(protoProps) _.extend(child.prototype, protoProps);

				// Set a convenience property in case the parent's prototype is needed
				// later.
				child.__super__ = parent.prototype;

				return child;
			};

			// Set up inheritance for the model, collection, router, view and history.
			Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;

			// Throw an error when a URL is needed, and none is supplied.
			var urlError = function() {
				throw new Error('A "url" property or function must be specified');
			};

			// Wrap an optional error callback with a fallback error event.
			var wrapError = function(model, options) {
				var error = options.error;
				options.error = function(resp) {
					if(error) error(model, resp, options);
					model.trigger('error', model, resp, options);
				};
			};

			return Backbone;

		}));

	}, {
		"underscore": 5
	}],
	2: [function(require, module, exports) {

		/**
		 * @license
		 *
		 * chroma.js - JavaScript library for color conversions
		 * 
		 * Copyright (c) 2011-2015, Gregor Aisch
		 * All rights reserved.
		 * 
		 * Redistribution and use in source and binary forms, with or without
		 * modification, are permitted provided that the following conditions are met:
		 * 
		 * 1. Redistributions of source code must retain the above copyright notice, this
		 *    list of conditions and the following disclaimer.
		 * 
		 * 2. Redistributions in binary form must reproduce the above copyright notice,
		 *    this list of conditions and the following disclaimer in the documentation
		 *    and/or other materials provided with the distribution.
		 * 
		 * 3. The name Gregor Aisch may not be used to endorse or promote products
		 *    derived from this software without specific prior written permission.
		 * 
		 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
		 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
		 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
		 * DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
		 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
		 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
		 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
		 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
		 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
		 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
		 *
		 */

		/*
		    chroma.js

		    Copyright (c) 2011-2013, Gregor Aisch
		    All rights reserved.

		    Redistribution and use in source and binary forms, with or without
		    modification, are permitted provided that the following conditions are met:

		    * Redistributions of source code must retain the above copyright notice, this
		      list of conditions and the following disclaimer.

		    * Redistributions in binary form must reproduce the above copyright notice,
		      this list of conditions and the following disclaimer in the documentation
		      and/or other materials provided with the distribution.

		    * The name Gregor Aisch may not be used to endorse or promote products
		      derived from this software without specific prior written permission.

		    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
		    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
		    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
		    DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
		    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
		    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
		    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
		    OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
		    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
		    EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

		    @source: https://github.com/gka/chroma.js
		 */

		(function() {
			var Color, LAB_CONSTANTS, PI, PITHIRD, TWOPI, _guess_formats, _guess_formats_sorted, _input, _interpolators, abs, atan2, bezier, blend, blend_f, brewer, burn, chroma, clip_rgb, cmyk2rgb, colors, cos, css2rgb, darken, dodge, each, floor, hex2rgb, hsi2rgb, hsl2css, hsl2rgb, hsv2rgb, interpolate, interpolate_hsx, interpolate_lab, interpolate_num, interpolate_rgb, lab2lch, lab2rgb, lab_xyz, lch2lab, lch2rgb, lighten, limit, log, luminance_x, m, max, multiply, normal, num2rgb, overlay, pow, rgb2cmyk, rgb2css, rgb2hex, rgb2hsi, rgb2hsl, rgb2hsv, rgb2lab, rgb2lch, rgb2luminance, rgb2num, rgb2temperature, rgb2xyz, rgb_xyz, rnd, root, round, screen, sin, sqrt, temperature2rgb, type, unpack, w3cx11, xyz_lab, xyz_rgb,
				slice = [].slice;

			type = (function() {

				/*
				for browser-safe type checking+
				ported from jQuery's $.type
				 */
				var classToType, len, name, o, ref;
				classToType = {};
				ref = "Boolean Number String Function Array Date RegExp Undefined Null".split(" ");
				for(o = 0, len = ref.length; o < len; o++) {
					name = ref[o];
					classToType["[object " + name + "]"] = name.toLowerCase();
				}
				return function(obj) {
					var strType;
					strType = Object.prototype.toString.call(obj);
					return classToType[strType] || "object";
				};
			})();

			limit = function(x, min, max) {
				if(min == null) {
					min = 0;
				}
				if(max == null) {
					max = 1;
				}
				if(x < min) {
					x = min;
				}
				if(x > max) {
					x = max;
				}
				return x;
			};

			unpack = function(args) {
				if(args.length >= 3) {
					return [].slice.call(args);
				} else {
					return args[0];
				}
			};

			clip_rgb = function(rgb) {
				var i;
				for(i in rgb) {
					if(i < 3) {
						if(rgb[i] < 0) {
							rgb[i] = 0;
						}
						if(rgb[i] > 255) {
							rgb[i] = 255;
						}
					} else if(i === 3) {
						if(rgb[i] < 0) {
							rgb[i] = 0;
						}
						if(rgb[i] > 1) {
							rgb[i] = 1;
						}
					}
				}
				return rgb;
			};

			PI = Math.PI, round = Math.round, cos = Math.cos, floor = Math.floor, pow = Math.pow, log = Math.log, sin = Math.sin, sqrt = Math.sqrt, atan2 = Math.atan2, max = Math.max, abs = Math.abs;

			TWOPI = PI * 2;

			PITHIRD = PI / 3;

			chroma = function() {
				if(arguments[0] instanceof Color) {
					return arguments[0];
				}
				return(function(func, args, ctor) {
					ctor.prototype = func.prototype;
					var child = new ctor,
						result = func.apply(child, args);
					return Object(result) === result ? result : child;
				})(Color, arguments, function() {});
			};

			_interpolators = [];

			if((typeof module !== "undefined" && module !== null) && (module.exports != null)) {
				module.exports = chroma;
			}

			if(typeof define === 'function' && define.amd) {
				define([], function() {
					return chroma;
				});
			} else {
				root = typeof exports !== "undefined" && exports !== null ? exports : this;
				root.chroma = chroma;
			}

			chroma.version = '1.0.1';

			/**
			    chroma.js
  
			    Copyright (c) 2011-2013, Gregor Aisch
			    All rights reserved.
  
			    Redistribution and use in source and binary forms, with or without
			    modification, are permitted provided that the following conditions are met:
  
			    * Redistributions of source code must retain the above copyright notice, this
			      list of conditions and the following disclaimer.
  
			    * Redistributions in binary form must reproduce the above copyright notice,
			      this list of conditions and the following disclaimer in the documentation
			      and/or other materials provided with the distribution.
  
			    * The name Gregor Aisch may not be used to endorse or promote products
			      derived from this software without specific prior written permission.
  
			    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
			    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
			    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
			    DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
			    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
			    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
			    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
			    OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
			    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
			    EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
			    @source: https://github.com/gka/chroma.js
			 */

			_input = {};

			_guess_formats = [];

			_guess_formats_sorted = false;

			Color = (function() {
				function Color() {
					var arg, args, chk, len, len1, me, mode, o, w;
					me = this;
					args = [];
					for(o = 0, len = arguments.length; o < len; o++) {
						arg = arguments[o];
						if(arg != null) {
							args.push(arg);
						}
					}
					mode = args[args.length - 1];
					if(_input[mode] != null) {
						me._rgb = clip_rgb(_input[mode](unpack(args.slice(0, -1))));
					} else {
						if(!_guess_formats_sorted) {
							_guess_formats = _guess_formats.sort(function(a, b) {
								return b.p - a.p;
							});
							_guess_formats_sorted = true;
						}
						for(w = 0, len1 = _guess_formats.length; w < len1; w++) {
							chk = _guess_formats[w];
							mode = chk.test.apply(chk, args);
							if(mode) {
								break;
							}
						}
						if(mode) {
							me._rgb = clip_rgb(_input[mode].apply(_input, args));
						}
					}
					if(me._rgb == null) {
						console.warn('unknown format: ' + args);
					}
					if(me._rgb == null) {
						me._rgb = [0, 0, 0];
					}
					if(me._rgb.length === 3) {
						me._rgb.push(1);
					}
				}

				Color.prototype.alpha = function(alpha) {
					if(arguments.length) {
						this._rgb[3] = alpha;
						return this;
					}
					return this._rgb[3];
				};

				Color.prototype.toString = function() {
					return this.name();
				};

				return Color;

			})();

			/**
				ColorBrewer colors for chroma.js
  
				Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The 
				Pennsylvania State University.
  
				Licensed under the Apache License, Version 2.0 (the "License"); 
				you may not use this file except in compliance with the License.
				You may obtain a copy of the License at	
				http://www.apache.org/licenses/LICENSE-2.0
  
				Unless required by applicable law or agreed to in writing, software distributed
				under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
				CONDITIONS OF ANY KIND, either express or implied. See the License for the
				specific language governing permissions and limitations under the License.
  
			    @preserve
			 */

			chroma.brewer = brewer = {
				OrRd: ['#fff7ec', '#fee8c8', '#fdd49e', '#fdbb84', '#fc8d59', '#ef6548', '#d7301f', '#b30000', '#7f0000'],
				PuBu: ['#fff7fb', '#ece7f2', '#d0d1e6', '#a6bddb', '#74a9cf', '#3690c0', '#0570b0', '#045a8d', '#023858'],
				BuPu: ['#f7fcfd', '#e0ecf4', '#bfd3e6', '#9ebcda', '#8c96c6', '#8c6bb1', '#88419d', '#810f7c', '#4d004b'],
				Oranges: ['#fff5eb', '#fee6ce', '#fdd0a2', '#fdae6b', '#fd8d3c', '#f16913', '#d94801', '#a63603', '#7f2704'],
				BuGn: ['#f7fcfd', '#e5f5f9', '#ccece6', '#99d8c9', '#66c2a4', '#41ae76', '#238b45', '#006d2c', '#00441b'],
				YlOrBr: ['#ffffe5', '#fff7bc', '#fee391', '#fec44f', '#fe9929', '#ec7014', '#cc4c02', '#993404', '#662506'],
				YlGn: ['#ffffe5', '#f7fcb9', '#d9f0a3', '#addd8e', '#78c679', '#41ab5d', '#238443', '#006837', '#004529'],
				Reds: ['#fff5f0', '#fee0d2', '#fcbba1', '#fc9272', '#fb6a4a', '#ef3b2c', '#cb181d', '#a50f15', '#67000d'],
				RdPu: ['#fff7f3', '#fde0dd', '#fcc5c0', '#fa9fb5', '#f768a1', '#dd3497', '#ae017e', '#7a0177', '#49006a'],
				Greens: ['#f7fcf5', '#e5f5e0', '#c7e9c0', '#a1d99b', '#74c476', '#41ab5d', '#238b45', '#006d2c', '#00441b'],
				YlGnBu: ['#ffffd9', '#edf8b1', '#c7e9b4', '#7fcdbb', '#41b6c4', '#1d91c0', '#225ea8', '#253494', '#081d58'],
				Purples: ['#fcfbfd', '#efedf5', '#dadaeb', '#bcbddc', '#9e9ac8', '#807dba', '#6a51a3', '#54278f', '#3f007d'],
				GnBu: ['#f7fcf0', '#e0f3db', '#ccebc5', '#a8ddb5', '#7bccc4', '#4eb3d3', '#2b8cbe', '#0868ac', '#084081'],
				Greys: ['#ffffff', '#f0f0f0', '#d9d9d9', '#bdbdbd', '#969696', '#737373', '#525252', '#252525', '#000000'],
				YlOrRd: ['#ffffcc', '#ffeda0', '#fed976', '#feb24c', '#fd8d3c', '#fc4e2a', '#e31a1c', '#bd0026', '#800026'],
				PuRd: ['#f7f4f9', '#e7e1ef', '#d4b9da', '#c994c7', '#df65b0', '#e7298a', '#ce1256', '#980043', '#67001f'],
				Blues: ['#f7fbff', '#deebf7', '#c6dbef', '#9ecae1', '#6baed6', '#4292c6', '#2171b5', '#08519c', '#08306b'],
				PuBuGn: ['#fff7fb', '#ece2f0', '#d0d1e6', '#a6bddb', '#67a9cf', '#3690c0', '#02818a', '#016c59', '#014636'],
				Spectral: ['#9e0142', '#d53e4f', '#f46d43', '#fdae61', '#fee08b', '#ffffbf', '#e6f598', '#abdda4', '#66c2a5', '#3288bd', '#5e4fa2'],
				RdYlGn: ['#a50026', '#d73027', '#f46d43', '#fdae61', '#fee08b', '#ffffbf', '#d9ef8b', '#a6d96a', '#66bd63', '#1a9850', '#006837'],
				RdBu: ['#67001f', '#b2182b', '#d6604d', '#f4a582', '#fddbc7', '#f7f7f7', '#d1e5f0', '#92c5de', '#4393c3', '#2166ac', '#053061'],
				PiYG: ['#8e0152', '#c51b7d', '#de77ae', '#f1b6da', '#fde0ef', '#f7f7f7', '#e6f5d0', '#b8e186', '#7fbc41', '#4d9221', '#276419'],
				PRGn: ['#40004b', '#762a83', '#9970ab', '#c2a5cf', '#e7d4e8', '#f7f7f7', '#d9f0d3', '#a6dba0', '#5aae61', '#1b7837', '#00441b'],
				RdYlBu: ['#a50026', '#d73027', '#f46d43', '#fdae61', '#fee090', '#ffffbf', '#e0f3f8', '#abd9e9', '#74add1', '#4575b4', '#313695'],
				BrBG: ['#543005', '#8c510a', '#bf812d', '#dfc27d', '#f6e8c3', '#f5f5f5', '#c7eae5', '#80cdc1', '#35978f', '#01665e', '#003c30'],
				RdGy: ['#67001f', '#b2182b', '#d6604d', '#f4a582', '#fddbc7', '#ffffff', '#e0e0e0', '#bababa', '#878787', '#4d4d4d', '#1a1a1a'],
				PuOr: ['#7f3b08', '#b35806', '#e08214', '#fdb863', '#fee0b6', '#f7f7f7', '#d8daeb', '#b2abd2', '#8073ac', '#542788', '#2d004b'],
				Set2: ['#66c2a5', '#fc8d62', '#8da0cb', '#e78ac3', '#a6d854', '#ffd92f', '#e5c494', '#b3b3b3'],
				Accent: ['#7fc97f', '#beaed4', '#fdc086', '#ffff99', '#386cb0', '#f0027f', '#bf5b17', '#666666'],
				Set1: ['#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00', '#ffff33', '#a65628', '#f781bf', '#999999'],
				Set3: ['#8dd3c7', '#ffffb3', '#bebada', '#fb8072', '#80b1d3', '#fdb462', '#b3de69', '#fccde5', '#d9d9d9', '#bc80bd', '#ccebc5', '#ffed6f'],
				Dark2: ['#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e', '#e6ab02', '#a6761d', '#666666'],
				Paired: ['#a6cee3', '#1f78b4', '#b2df8a', '#33a02c', '#fb9a99', '#e31a1c', '#fdbf6f', '#ff7f00', '#cab2d6', '#6a3d9a', '#ffff99', '#b15928'],
				Pastel2: ['#b3e2cd', '#fdcdac', '#cbd5e8', '#f4cae4', '#e6f5c9', '#fff2ae', '#f1e2cc', '#cccccc'],
				Pastel1: ['#fbb4ae', '#b3cde3', '#ccebc5', '#decbe4', '#fed9a6', '#ffffcc', '#e5d8bd', '#fddaec', '#f2f2f2']
			};

			/**
				X11 color names
  
				http://www.w3.org/TR/css3-color/#svg-color
			 */

			w3cx11 = {
				indigo: "#4b0082",
				gold: "#ffd700",
				hotpink: "#ff69b4",
				firebrick: "#b22222",
				indianred: "#cd5c5c",
				yellow: "#ffff00",
				mistyrose: "#ffe4e1",
				darkolivegreen: "#556b2f",
				olive: "#808000",
				darkseagreen: "#8fbc8f",
				pink: "#ffc0cb",
				tomato: "#ff6347",
				lightcoral: "#f08080",
				orangered: "#ff4500",
				navajowhite: "#ffdead",
				lime: "#00ff00",
				palegreen: "#98fb98",
				darkslategrey: "#2f4f4f",
				greenyellow: "#adff2f",
				burlywood: "#deb887",
				seashell: "#fff5ee",
				mediumspringgreen: "#00fa9a",
				fuchsia: "#ff00ff",
				papayawhip: "#ffefd5",
				blanchedalmond: "#ffebcd",
				chartreuse: "#7fff00",
				dimgray: "#696969",
				black: "#000000",
				peachpuff: "#ffdab9",
				springgreen: "#00ff7f",
				aquamarine: "#7fffd4",
				white: "#ffffff",
				orange: "#ffa500",
				lightsalmon: "#ffa07a",
				darkslategray: "#2f4f4f",
				brown: "#a52a2a",
				ivory: "#fffff0",
				dodgerblue: "#1e90ff",
				peru: "#cd853f",
				lawngreen: "#7cfc00",
				chocolate: "#d2691e",
				crimson: "#dc143c",
				forestgreen: "#228b22",
				darkgrey: "#a9a9a9",
				lightseagreen: "#20b2aa",
				cyan: "#00ffff",
				mintcream: "#f5fffa",
				silver: "#c0c0c0",
				antiquewhite: "#faebd7",
				mediumorchid: "#ba55d3",
				skyblue: "#87ceeb",
				gray: "#808080",
				darkturquoise: "#00ced1",
				goldenrod: "#daa520",
				darkgreen: "#006400",
				floralwhite: "#fffaf0",
				darkviolet: "#9400d3",
				darkgray: "#a9a9a9",
				moccasin: "#ffe4b5",
				saddlebrown: "#8b4513",
				grey: "#808080",
				darkslateblue: "#483d8b",
				lightskyblue: "#87cefa",
				lightpink: "#ffb6c1",
				mediumvioletred: "#c71585",
				slategrey: "#708090",
				red: "#ff0000",
				deeppink: "#ff1493",
				limegreen: "#32cd32",
				darkmagenta: "#8b008b",
				palegoldenrod: "#eee8aa",
				plum: "#dda0dd",
				turquoise: "#40e0d0",
				lightgrey: "#d3d3d3",
				lightgoldenrodyellow: "#fafad2",
				darkgoldenrod: "#b8860b",
				lavender: "#e6e6fa",
				maroon: "#800000",
				yellowgreen: "#9acd32",
				sandybrown: "#f4a460",
				thistle: "#d8bfd8",
				violet: "#ee82ee",
				navy: "#000080",
				magenta: "#ff00ff",
				dimgrey: "#696969",
				tan: "#d2b48c",
				rosybrown: "#bc8f8f",
				olivedrab: "#6b8e23",
				blue: "#0000ff",
				lightblue: "#add8e6",
				ghostwhite: "#f8f8ff",
				honeydew: "#f0fff0",
				cornflowerblue: "#6495ed",
				slateblue: "#6a5acd",
				linen: "#faf0e6",
				darkblue: "#00008b",
				powderblue: "#b0e0e6",
				seagreen: "#2e8b57",
				darkkhaki: "#bdb76b",
				snow: "#fffafa",
				sienna: "#a0522d",
				mediumblue: "#0000cd",
				royalblue: "#4169e1",
				lightcyan: "#e0ffff",
				green: "#008000",
				mediumpurple: "#9370db",
				midnightblue: "#191970",
				cornsilk: "#fff8dc",
				paleturquoise: "#afeeee",
				bisque: "#ffe4c4",
				slategray: "#708090",
				darkcyan: "#008b8b",
				khaki: "#f0e68c",
				wheat: "#f5deb3",
				teal: "#008080",
				darkorchid: "#9932cc",
				deepskyblue: "#00bfff",
				salmon: "#fa8072",
				darkred: "#8b0000",
				steelblue: "#4682b4",
				palevioletred: "#db7093",
				lightslategray: "#778899",
				aliceblue: "#f0f8ff",
				lightslategrey: "#778899",
				lightgreen: "#90ee90",
				orchid: "#da70d6",
				gainsboro: "#dcdcdc",
				mediumseagreen: "#3cb371",
				lightgray: "#d3d3d3",
				mediumturquoise: "#48d1cc",
				lemonchiffon: "#fffacd",
				cadetblue: "#5f9ea0",
				lightyellow: "#ffffe0",
				lavenderblush: "#fff0f5",
				coral: "#ff7f50",
				purple: "#800080",
				aqua: "#00ffff",
				whitesmoke: "#f5f5f5",
				mediumslateblue: "#7b68ee",
				darkorange: "#ff8c00",
				mediumaquamarine: "#66cdaa",
				darksalmon: "#e9967a",
				beige: "#f5f5dc",
				blueviolet: "#8a2be2",
				azure: "#f0ffff",
				lightsteelblue: "#b0c4de",
				oldlace: "#fdf5e6",
				rebeccapurple: "#663399"
			};

			chroma.colors = colors = w3cx11;

			lab2rgb = function() {
				var a, b, g, l, r, ref, ref1, ref2, x, y, z;
				ref = unpack(arguments), l = ref[0], a = ref[1], b = ref[2];

				/*
				adapted to match d3 implementation
				 */
				if(l !== void 0 && l.length === 3) {
					ref1 = l, l = ref1[0], a = ref1[1], b = ref1[2];
				}
				if(l !== void 0 && l.length === 3) {
					ref2 = l, l = ref2[0], a = ref2[1], b = ref2[2];
				}
				y = (l + 16) / 116;
				x = y + a / 500;
				z = y - b / 200;
				x = lab_xyz(x) * LAB_CONSTANTS.X;
				y = lab_xyz(y) * LAB_CONSTANTS.Y;
				z = lab_xyz(z) * LAB_CONSTANTS.Z;
				r = xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z);
				g = xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z);
				b = xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z);
				return [limit(r, 0, 255), limit(g, 0, 255), limit(b, 0, 255), 1];
			};

			lab_xyz = function(x) {
				if(x > 0.206893034) {
					return x * x * x;
				} else {
					return(x - 4 / 29) / 7.787037;
				}
			};

			xyz_rgb = function(r) {
				return round(255 * (r <= 0.00304 ? 12.92 * r : 1.055 * pow(r, 1 / 2.4) - 0.055));
			};

			LAB_CONSTANTS = {
				K: 18,
				X: 0.950470,
				Y: 1,
				Z: 1.088830
			};

			rgb2lab = function() {
				var b, g, r, ref, ref1, x, y, z;
				ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
				ref1 = rgb2xyz(r, g, b), x = ref1[0], y = ref1[1], z = ref1[2];
				return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
			};

			rgb_xyz = function(r) {
				if((r /= 255) <= 0.04045) {
					return r / 12.92;
				} else {
					return pow((r + 0.055) / 1.055, 2.4);
				}
			};

			xyz_lab = function(x) {
				if(x > 0.008856) {
					return pow(x, 1 / 3);
				} else {
					return 7.787037 * x + 4 / 29;
				}
			};

			rgb2xyz = function() {
				var b, g, r, ref, x, y, z;
				ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
				r = rgb_xyz(r);
				g = rgb_xyz(g);
				b = rgb_xyz(b);
				x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / LAB_CONSTANTS.X);
				y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / LAB_CONSTANTS.Y);
				z = xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / LAB_CONSTANTS.Z);
				return [x, y, z];
			};

			chroma.lab = function() {
				return(function(func, args, ctor) {
					ctor.prototype = func.prototype;
					var child = new ctor,
						result = func.apply(child, args);
					return Object(result) === result ? result : child;
				})(Color, slice.call(arguments).concat(['lab']), function() {});
			};

			_input.lab = lab2rgb;

			Color.prototype.lab = function() {
				return rgb2lab(this._rgb);
			};

			bezier = function(colors) {
				var I, I0, I1, c, lab0, lab1, lab2, lab3, ref, ref1, ref2;
				colors = (function() {
					var len, o, results;
					results = [];
					for(o = 0, len = colors.length; o < len; o++) {
						c = colors[o];
						results.push(chroma(c));
					}
					return results;
				})();
				if(colors.length === 2) {
					ref = (function() {
						var len, o, results;
						results = [];
						for(o = 0, len = colors.length; o < len; o++) {
							c = colors[o];
							results.push(c.lab());
						}
						return results;
					})(), lab0 = ref[0], lab1 = ref[1];
					I = function(t) {
						var i, lab;
						lab = (function() {
							var o, results;
							results = [];
							for(i = o = 0; o <= 2; i = ++o) {
								results.push(lab0[i] + t * (lab1[i] - lab0[i]));
							}
							return results;
						})();
						return chroma.lab.apply(chroma, lab);
					};
				} else if(colors.length === 3) {
					ref1 = (function() {
						var len, o, results;
						results = [];
						for(o = 0, len = colors.length; o < len; o++) {
							c = colors[o];
							results.push(c.lab());
						}
						return results;
					})(), lab0 = ref1[0], lab1 = ref1[1], lab2 = ref1[2];
					I = function(t) {
						var i, lab;
						lab = (function() {
							var o, results;
							results = [];
							for(i = o = 0; o <= 2; i = ++o) {
								results.push((1 - t) * (1 - t) * lab0[i] + 2 * (1 - t) * t * lab1[i] + t * t * lab2[i]);
							}
							return results;
						})();
						return chroma.lab.apply(chroma, lab);
					};
				} else if(colors.length === 4) {
					ref2 = (function() {
						var len, o, results;
						results = [];
						for(o = 0, len = colors.length; o < len; o++) {
							c = colors[o];
							results.push(c.lab());
						}
						return results;
					})(), lab0 = ref2[0], lab1 = ref2[1], lab2 = ref2[2], lab3 = ref2[3];
					I = function(t) {
						var i, lab;
						lab = (function() {
							var o, results;
							results = [];
							for(i = o = 0; o <= 2; i = ++o) {
								results.push((1 - t) * (1 - t) * (1 - t) * lab0[i] + 3 * (1 - t) * (1 - t) * t * lab1[i] + 3 * (1 - t) * t * t * lab2[i] + t * t * t * lab3[i]);
							}
							return results;
						})();
						return chroma.lab.apply(chroma, lab);
					};
				} else if(colors.length === 5) {
					I0 = bezier(colors.slice(0, 3));
					I1 = bezier(colors.slice(2, 5));
					I = function(t) {
						if(t < 0.5) {
							return I0(t * 2);
						} else {
							return I1((t - 0.5) * 2);
						}
					};
				}
				return I;
			};

			chroma.bezier = bezier;

			/*
			    chroma.js
  
			    Copyright (c) 2011-2013, Gregor Aisch
			    All rights reserved.
  
			    Redistribution and use in source and binary forms, with or without
			    modification, are permitted provided that the following conditions are met:
  
			    * Redistributions of source code must retain the above copyright notice, this
			      list of conditions and the following disclaimer.
  
			    * Redistributions in binary form must reproduce the above copyright notice,
			      this list of conditions and the following disclaimer in the documentation
			      and/or other materials provided with the distribution.
  
			    * The name Gregor Aisch may not be used to endorse or promote products
			      derived from this software without specific prior written permission.
  
			    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
			    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
			    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
			    DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
			    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
			    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
			    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
			    OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
			    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
			    EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
			    @source: https://github.com/gka/chroma.js
			 */

			chroma.cubehelix = function(start, rotations, hue, gamma, lightness) {
				var dh, dl;
				if(start == null) {
					start = 300;
				}
				if(rotations == null) {
					rotations = -1.5;
				}
				if(hue == null) {
					hue = 1;
				}
				if(gamma == null) {
					gamma = 1;
				}
				if(lightness == null) {
					lightness = [0, 1];
				}
				dl = lightness[1] - lightness[0];
				if(type(hue) === 'array') {
					dh = hue[1] - hue[0];
					if(dh === 0) {
						hue = hue[1];
					}
				} else {
					dh = 0;
				}
				return function(fract) {
					var a, amp, b, cos_a, g, h, l, r, sin_a;
					a = TWOPI * ((start + 120) / 360 + rotations * fract);
					l = pow(lightness[0] + dl * fract, gamma);
					h = dh !== 0 ? hue[0] + fract * dh : hue;
					amp = h * l * (1 - l) / 2;
					cos_a = cos(a);
					sin_a = sin(a);
					r = l + amp * (-0.14861 * cos_a + 1.78277 * sin_a);
					g = l + amp * (-0.29227 * cos_a - 0.90649 * sin_a);
					b = l + amp * (+1.97294 * cos_a);
					return chroma(clip_rgb([r * 255, g * 255, b * 255]));
				};
			};

			chroma.random = function() {
				var code, digits, i, o;
				digits = '0123456789abcdef';
				code = '#';
				for(i = o = 0; o < 6; i = ++o) {
					code += digits.charAt(floor(Math.random() * 16));
				}
				return new Color(code);
			};

			_input.rgb = function() {
				var k, ref, results, v;
				ref = unpack(arguments);
				results = [];
				for(k in ref) {
					v = ref[k];
					results.push(v);
				}
				return results;
			};

			chroma.rgb = function() {
				return(function(func, args, ctor) {
					ctor.prototype = func.prototype;
					var child = new ctor,
						result = func.apply(child, args);
					return Object(result) === result ? result : child;
				})(Color, slice.call(arguments).concat(['rgb']), function() {});
			};

			Color.prototype.rgb = function() {
				return this._rgb.slice(0, 3);
			};

			Color.prototype.rgba = function() {
				return this._rgb;
			};

			_guess_formats.push({
				p: 15,
				test: function(n) {
					var a;
					a = unpack(arguments);
					if(type(a) === 'array' && a.length === 3) {
						return 'rgb';
					}
					if(a.length === 4 && type(a[3]) === "number" && a[3] >= 0 && a[3] <= 1) {
						return 'rgb';
					}
				}
			});

			hex2rgb = function(hex) {
				var a, b, g, r, rgb, u;
				if(hex.match(/^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)) {
					if(hex.length === 4 || hex.length === 7) {
						hex = hex.substr(1);
					}
					if(hex.length === 3) {
						hex = hex.split("");
						hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
					}
					u = parseInt(hex, 16);
					r = u >> 16;
					g = u >> 8 & 0xFF;
					b = u & 0xFF;
					return [r, g, b, 1];
				}
				if(hex.match(/^#?([A-Fa-f0-9]{8})$/)) {
					if(hex.length === 9) {
						hex = hex.substr(1);
					}
					u = parseInt(hex, 16);
					r = u >> 24 & 0xFF;
					g = u >> 16 & 0xFF;
					b = u >> 8 & 0xFF;
					a = round((u & 0xFF) / 0xFF * 100) / 100;
					return [r, g, b, a];
				}
				if((_input.css != null) && (rgb = _input.css(hex))) {
					return rgb;
				}
				throw "unknown color: " + hex;
			};

			rgb2hex = function(channels, mode) {
				var a, b, g, hxa, r, str, u;
				if(mode == null) {
					mode = 'rgb';
				}
				r = channels[0], g = channels[1], b = channels[2], a = channels[3];
				u = r << 16 | g << 8 | b;
				str = "000000" + u.toString(16);
				str = str.substr(str.length - 6);
				hxa = '0' + round(a * 255).toString(16);
				hxa = hxa.substr(hxa.length - 2);
				return "#" + (function() {
					switch(mode.toLowerCase()) {
						case 'rgba':
							return str + hxa;
						case 'argb':
							return hxa + str;
						default:
							return str;
					}
				})();
			};

			_input.hex = function(h) {
				return hex2rgb(h);
			};

			chroma.hex = function() {
				return(function(func, args, ctor) {
					ctor.prototype = func.prototype;
					var child = new ctor,
						result = func.apply(child, args);
					return Object(result) === result ? result : child;
				})(Color, slice.call(arguments).concat(['hex']), function() {});
			};

			Color.prototype.hex = function(mode) {
				if(mode == null) {
					mode = 'rgb';
				}
				return rgb2hex(this._rgb, mode);
			};

			_guess_formats.push({
				p: 10,
				test: function(n) {
					if(arguments.length === 1 && type(n) === "string") {
						return 'hex';
					}
				}
			});

			hsl2rgb = function() {
				var b, c, g, h, i, l, o, r, ref, ref1, s, t1, t2, t3;
				ref = unpack(arguments), h = ref[0], s = ref[1], l = ref[2];
				if(s === 0) {
					r = g = b = l * 255;
				} else {
					t3 = [0, 0, 0];
					c = [0, 0, 0];
					t2 = l < 0.5 ? l * (1 + s) : l + s - l * s;
					t1 = 2 * l - t2;
					h /= 360;
					t3[0] = h + 1 / 3;
					t3[1] = h;
					t3[2] = h - 1 / 3;
					for(i = o = 0; o <= 2; i = ++o) {
						if(t3[i] < 0) {
							t3[i] += 1;
						}
						if(t3[i] > 1) {
							t3[i] -= 1;
						}
						if(6 * t3[i] < 1) {
							c[i] = t1 + (t2 - t1) * 6 * t3[i];
						} else if(2 * t3[i] < 1) {
							c[i] = t2;
						} else if(3 * t3[i] < 2) {
							c[i] = t1 + (t2 - t1) * ((2 / 3) - t3[i]) * 6;
						} else {
							c[i] = t1;
						}
					}
					ref1 = [round(c[0] * 255), round(c[1] * 255), round(c[2] * 255)], r = ref1[0], g = ref1[1], b = ref1[2];
				}
				if(arguments.length === 4) {
					return [r, g, b, arguments[3]];
				} else {
					return [r, g, b];
				}
			};

			rgb2hsl = function(r, g, b) {
				var h, l, min, ref, s;
				if(r !== void 0 && r.length >= 3) {
					ref = r, r = ref[0], g = ref[1], b = ref[2];
				}
				r /= 255;
				g /= 255;
				b /= 255;
				min = Math.min(r, g, b);
				max = Math.max(r, g, b);
				l = (max + min) / 2;
				if(max === min) {
					s = 0;
					h = Number.NaN;
				} else {
					s = l < 0.5 ? (max - min) / (max + min) : (max - min) / (2 - max - min);
				}
				if(r === max) {
					h = (g - b) / (max - min);
				} else if(g === max) {
					h = 2 + (b - r) / (max - min);
				} else if(b === max) {
					h = 4 + (r - g) / (max - min);
				}
				h *= 60;
				if(h < 0) {
					h += 360;
				}
				return [h, s, l];
			};

			chroma.hsl = function() {
				return(function(func, args, ctor) {
					ctor.prototype = func.prototype;
					var child = new ctor,
						result = func.apply(child, args);
					return Object(result) === result ? result : child;
				})(Color, slice.call(arguments).concat(['hsl']), function() {});
			};

			_input.hsl = hsl2rgb;

			Color.prototype.hsl = function() {
				return rgb2hsl(this._rgb);
			};

			hsv2rgb = function() {
				var b, f, g, h, i, p, q, r, ref, ref1, ref2, ref3, ref4, ref5, ref6, s, t, v;
				ref = unpack(arguments), h = ref[0], s = ref[1], v = ref[2];
				v *= 255;
				if(s === 0) {
					r = g = b = v;
				} else {
					if(h === 360) {
						h = 0;
					}
					if(h > 360) {
						h -= 360;
					}
					if(h < 0) {
						h += 360;
					}
					h /= 60;
					i = floor(h);
					f = h - i;
					p = v * (1 - s);
					q = v * (1 - s * f);
					t = v * (1 - s * (1 - f));
					switch(i) {
						case 0:
							ref1 = [v, t, p], r = ref1[0], g = ref1[1], b = ref1[2];
							break;
						case 1:
							ref2 = [q, v, p], r = ref2[0], g = ref2[1], b = ref2[2];
							break;
						case 2:
							ref3 = [p, v, t], r = ref3[0], g = ref3[1], b = ref3[2];
							break;
						case 3:
							ref4 = [p, q, v], r = ref4[0], g = ref4[1], b = ref4[2];
							break;
						case 4:
							ref5 = [t, p, v], r = ref5[0], g = ref5[1], b = ref5[2];
							break;
						case 5:
							ref6 = [v, p, q], r = ref6[0], g = ref6[1], b = ref6[2];
					}
				}
				r = round(r);
				g = round(g);
				b = round(b);
				return [r, g, b];
			};

			rgb2hsv = function() {
				var b, delta, g, h, min, r, ref, s, v;
				ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
				min = Math.min(r, g, b);
				max = Math.max(r, g, b);
				delta = max - min;
				v = max / 255.0;
				if(max === 0) {
					h = Number.NaN;
					s = 0;
				} else {
					s = delta / max;
					if(r === max) {
						h = (g - b) / delta;
					}
					if(g === max) {
						h = 2 + (b - r) / delta;
					}
					if(b === max) {
						h = 4 + (r - g) / delta;
					}
					h *= 60;
					if(h < 0) {
						h += 360;
					}
				}
				return [h, s, v];
			};

			chroma.hsv = function() {
				return(function(func, args, ctor) {
					ctor.prototype = func.prototype;
					var child = new ctor,
						result = func.apply(child, args);
					return Object(result) === result ? result : child;
				})(Color, slice.call(arguments).concat(['hsv']), function() {});
			};

			_input.hsv = hsv2rgb;

			Color.prototype.hsv = function() {
				return rgb2hsv(this._rgb);
			};

			num2rgb = function(num) {
				var b, g, r;
				if(type(num) === "number" && num >= 0 && num <= 0xFFFFFF) {
					r = num >> 16;
					g = (num >> 8) & 0xFF;
					b = num & 0xFF;
					return [r, g, b, 1];
				}
				console.warn("unknown num color: " + num);
				return [0, 0, 0, 1];
			};

			rgb2num = function() {
				var b, g, r, ref;
				ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
				return(r << 16) + (g << 8) + b;
			};

			chroma.num = function(num) {
				return new Color(num, 'num');
			};

			Color.prototype.num = function() {
				return rgb2num(this._rgb);
			};

			_input.num = num2rgb;

			_guess_formats.push({
				p: 10,
				test: function(n) {
					if(arguments.length === 1 && type(n) === "number" && n >= 0 && n <= 0xFFFFFF) {
						return 'num';
					}
				}
			});

			css2rgb = function(css) {
				var aa, ab, hsl, i, m, o, rgb, w;
				css = css.toLowerCase();
				if((chroma.colors != null) && chroma.colors[css]) {
					return hex2rgb(chroma.colors[css]);
				}
				if(m = css.match(/rgb\(\s*(\-?\d+),\s*(\-?\d+)\s*,\s*(\-?\d+)\s*\)/)) {
					rgb = m.slice(1, 4);
					for(i = o = 0; o <= 2; i = ++o) {
						rgb[i] = +rgb[i];
					}
					rgb[3] = 1;
				} else if(m = css.match(/rgba\(\s*(\-?\d+),\s*(\-?\d+)\s*,\s*(\-?\d+)\s*,\s*([01]|[01]?\.\d+)\)/)) {
					rgb = m.slice(1, 5);
					for(i = w = 0; w <= 3; i = ++w) {
						rgb[i] = +rgb[i];
					}
				} else if(m = css.match(/rgb\(\s*(\-?\d+(?:\.\d+)?)%,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*\)/)) {
					rgb = m.slice(1, 4);
					for(i = aa = 0; aa <= 2; i = ++aa) {
						rgb[i] = round(rgb[i] * 2.55);
					}
					rgb[3] = 1;
				} else if(m = css.match(/rgba\(\s*(\-?\d+(?:\.\d+)?)%,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)/)) {
					rgb = m.slice(1, 5);
					for(i = ab = 0; ab <= 2; i = ++ab) {
						rgb[i] = round(rgb[i] * 2.55);
					}
					rgb[3] = +rgb[3];
				} else if(m = css.match(/hsl\(\s*(\-?\d+(?:\.\d+)?),\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*\)/)) {
					hsl = m.slice(1, 4);
					hsl[1] *= 0.01;
					hsl[2] *= 0.01;
					rgb = hsl2rgb(hsl);
					rgb[3] = 1;
				} else if(m = css.match(/hsla\(\s*(\-?\d+(?:\.\d+)?),\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)/)) {
					hsl = m.slice(1, 4);
					hsl[1] *= 0.01;
					hsl[2] *= 0.01;
					rgb = hsl2rgb(hsl);
					rgb[3] = +m[4];
				}
				return rgb;
			};

			rgb2css = function(rgba) {
				var mode;
				mode = rgba[3] < 1 ? 'rgba' : 'rgb';
				if(mode === 'rgb') {
					return mode + '(' + rgba.slice(0, 3).map(round).join(',') + ')';
				} else if(mode === 'rgba') {
					return mode + '(' + rgba.slice(0, 3).map(round).join(',') + ',' + rgba[3] + ')';
				} else {

				}
			};

			rnd = function(a) {
				return round(a * 100) / 100;
			};

			hsl2css = function(hsl, alpha) {
				var mode;
				mode = alpha < 1 ? 'hsla' : 'hsl';
				hsl[0] = rnd(hsl[0] || 0);
				hsl[1] = rnd(hsl[1] * 100) + '%';
				hsl[2] = rnd(hsl[2] * 100) + '%';
				if(mode === 'hsla') {
					hsl[3] = alpha;
				}
				return mode + '(' + hsl.join(',') + ')';
			};

			_input.css = function(h) {
				return css2rgb(h);
			};

			chroma.css = function() {
				return(function(func, args, ctor) {
					ctor.prototype = func.prototype;
					var child = new ctor,
						result = func.apply(child, args);
					return Object(result) === result ? result : child;
				})(Color, slice.call(arguments).concat(['css']), function() {});
			};

			Color.prototype.css = function(mode) {
				if(mode == null) {
					mode = 'rgb';
				}
				if(mode.slice(0, 3) === 'rgb') {
					return rgb2css(this._rgb);
				} else if(mode.slice(0, 3) === 'hsl') {
					return hsl2css(this.hsl(), this.alpha());
				}
			};

			_input.named = function(name) {
				return hex2rgb(w3cx11[name]);
			};

			_guess_formats.push({
				p: 20,
				test: function(n) {
					if(arguments.length === 1 && (w3cx11[n] != null)) {
						return 'named';
					}
				}
			});

			Color.prototype.name = function(n) {
				var h, k;
				if(arguments.length) {
					if(w3cx11[n]) {
						this._rgb = hex2rgb(w3cx11[n]);
					}
					this._rgb[3] = 1;
					this;
				}
				h = this.hex();
				for(k in w3cx11) {
					if(h === w3cx11[k]) {
						return k;
					}
				}
				return h;
			};

			lch2lab = function() {

				/*
				Convert from a qualitative parameter h and a quantitative parameter l to a 24-bit pixel.
				These formulas were invented by David Dalrymple to obtain maximum contrast without going
				out of gamut if the parameters are in the range 0-1.
    
				A saturation multiplier was added by Gregor Aisch
				 */
				var c, h, l, ref;
				ref = unpack(arguments), l = ref[0], c = ref[1], h = ref[2];
				h = h * PI / 180;
				return [l, cos(h) * c, sin(h) * c];
			};

			lch2rgb = function() {
				var L, a, b, c, g, h, l, r, ref, ref1, ref2;
				ref = unpack(arguments), l = ref[0], c = ref[1], h = ref[2];
				ref1 = lch2lab(l, c, h), L = ref1[0], a = ref1[1], b = ref1[2];
				ref2 = lab2rgb(L, a, b), r = ref2[0], g = ref2[1], b = ref2[2];
				return [limit(r, 0, 255), limit(g, 0, 255), limit(b, 0, 255)];
			};

			lab2lch = function() {
				var a, b, c, h, l, ref;
				ref = unpack(arguments), l = ref[0], a = ref[1], b = ref[2];
				c = sqrt(a * a + b * b);
				h = (atan2(b, a) / PI * 180 + 360) % 360;
				return [l, c, h];
			};

			rgb2lch = function() {
				var a, b, g, l, r, ref, ref1;
				ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
				ref1 = rgb2lab(r, g, b), l = ref1[0], a = ref1[1], b = ref1[2];
				return lab2lch(l, a, b);
			};

			chroma.lch = function() {
				var args;
				args = unpack(arguments);
				return new Color(args, 'lch');
			};

			chroma.hcl = function() {
				var args;
				args = unpack(arguments);
				return new Color(args, 'hcl');
			};

			_input.lch = lch2rgb;

			_input.hcl = function() {
				var c, h, l, ref;
				ref = unpack(arguments), h = ref[0], c = ref[1], l = ref[2];
				return lch2rgb([l, c, h]);
			};

			Color.prototype.lch = function() {
				return rgb2lch(this._rgb);
			};

			Color.prototype.hcl = function() {
				return rgb2lch(this._rgb).reverse();
			};

			rgb2cmyk = function(mode) {
				var b, c, f, g, k, m, r, ref, y;
				if(mode == null) {
					mode = 'rgb';
				}
				ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
				r = r / 255;
				g = g / 255;
				b = b / 255;
				k = 1 - Math.max(r, Math.max(g, b));
				f = k < 1 ? 1 / (1 - k) : 0;
				c = (1 - r - k) * f;
				m = (1 - g - k) * f;
				y = (1 - b - k) * f;
				return [c, m, y, k];
			};

			cmyk2rgb = function() {
				var b, c, g, k, m, r, ref, y;
				ref = unpack(arguments), c = ref[0], m = ref[1], y = ref[2], k = ref[3];
				if(k === 1) {
					return [0, 0, 0];
				}
				r = c >= 1 ? 0 : round(255 * (1 - c) * (1 - k));
				g = m >= 1 ? 0 : round(255 * (1 - m) * (1 - k));
				b = y >= 1 ? 0 : round(255 * (1 - y) * (1 - k));
				return [r, g, b];
			};

			_input.cmyk = function() {
				var c, k, m, ref, y;
				ref = unpack(arguments), c = ref[0], m = ref[1], y = ref[2], k = ref[3];
				return cmyk2rgb(c, m, y, k);
			};

			chroma.cmyk = function() {
				return(function(func, args, ctor) {
					ctor.prototype = func.prototype;
					var child = new ctor,
						result = func.apply(child, args);
					return Object(result) === result ? result : child;
				})(Color, slice.call(arguments).concat(['cmyk']), function() {});
			};

			Color.prototype.cmyk = function() {
				return rgb2cmyk(this._rgb);
			};

			_input.gl = function() {
				var i, k, o, rgb, v;
				rgb = (function() {
					var ref, results;
					ref = unpack(arguments);
					results = [];
					for(k in ref) {
						v = ref[k];
						results.push(v);
					}
					return results;
				}).apply(this, arguments);
				for(i = o = 0; o <= 2; i = ++o) {
					rgb[i] *= 255;
				}
				return rgb;
			};

			chroma.gl = function() {
				return(function(func, args, ctor) {
					ctor.prototype = func.prototype;
					var child = new ctor,
						result = func.apply(child, args);
					return Object(result) === result ? result : child;
				})(Color, slice.call(arguments).concat(['gl']), function() {});
			};

			Color.prototype.gl = function() {
				var rgb;
				rgb = this._rgb;
				return [rgb[0] / 255, rgb[1] / 255, rgb[2] / 255, rgb[3]];
			};

			rgb2luminance = function(r, g, b) {
				var ref;
				ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
				r = luminance_x(r);
				g = luminance_x(g);
				b = luminance_x(b);
				return 0.2126 * r + 0.7152 * g + 0.0722 * b;
			};

			luminance_x = function(x) {
				x /= 255;
				if(x <= 0.03928) {
					return x / 12.92;
				} else {
					return pow((x + 0.055) / 1.055, 2.4);
				}
			};

			_interpolators = [];

			interpolate = function(col1, col2, f, m) {
				var interpol, len, o, res;
				if(f == null) {
					f = 0.5;
				}
				if(m == null) {
					m = 'rgb';
				}

				/*
				interpolates between colors
				f = 0 --> me
				f = 1 --> col
				 */
				if(type(col1) !== 'object') {
					col1 = chroma(col1);
				}
				if(type(col2) !== 'object') {
					col2 = chroma(col2);
				}
				for(o = 0, len = _interpolators.length; o < len; o++) {
					interpol = _interpolators[o];
					if(m === interpol[0]) {
						res = interpol[1](col1, col2, f, m);
						break;
					}
				}
				if(res == null) {
					throw "color mode " + m + " is not supported";
				}
				res.alpha(col1.alpha() + f * (col2.alpha() - col1.alpha()));
				return res;
			};

			chroma.interpolate = interpolate;

			Color.prototype.interpolate = function(col2, f, m) {
				return interpolate(this, col2, f, m);
			};

			chroma.mix = interpolate;

			Color.prototype.mix = Color.prototype.interpolate;

			interpolate_rgb = function(col1, col2, f, m) {
				var xyz0, xyz1;
				xyz0 = col1._rgb;
				xyz1 = col2._rgb;
				return new Color(xyz0[0] + f * (xyz1[0] - xyz0[0]), xyz0[1] + f * (xyz1[1] - xyz0[1]), xyz0[2] + f * (xyz1[2] - xyz0[2]), m);
			};

			_interpolators.push(['rgb', interpolate_rgb]);

			Color.prototype.luminance = function(lum, mode) {
				var cur_lum, eps, max_iter, test;
				if(mode == null) {
					mode = 'rgb';
				}
				if(!arguments.length) {
					return rgb2luminance(this._rgb);
				}
				if(lum === 0) {
					this._rgb = [0, 0, 0, this._rgb[3]];
				} else if(lum === 1) {
					this._rgb = [255, 255, 255, this._rgb[3]];
				} else {
					eps = 1e-7;
					max_iter = 20;
					test = function(l, h) {
						var lm, m;
						m = l.interpolate(h, 0.5, mode);
						lm = m.luminance();
						if(Math.abs(lum - lm) < eps || !max_iter--) {
							return m;
						}
						if(lm > lum) {
							return test(l, m);
						}
						return test(m, h);
					};
					cur_lum = rgb2luminance(this._rgb);
					this._rgb = (cur_lum > lum ? test(chroma('black'), this) : test(this, chroma('white'))).rgba();
				}
				return this;
			};

			temperature2rgb = function(kelvin) {
				var b, g, r, temp;
				temp = kelvin / 100;
				if(temp < 66) {
					r = 255;
					g = -155.25485562709179 - 0.44596950469579133 * (g = temp - 2) + 104.49216199393888 * log(g);
					b = temp < 20 ? 0 : -254.76935184120902 + 0.8274096064007395 * (b = temp - 10) + 115.67994401066147 * log(b);
				} else {
					r = 351.97690566805693 + 0.114206453784165 * (r = temp - 55) - 40.25366309332127 * log(r);
					g = 325.4494125711974 + 0.07943456536662342 * (g = temp - 50) - 28.0852963507957 * log(g);
					b = 255;
				}
				return clip_rgb([r, g, b]);
			};

			rgb2temperature = function() {
				var b, eps, g, maxTemp, minTemp, r, ref, rgb, temp;
				ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
				minTemp = 1000;
				maxTemp = 40000;
				eps = 0.4;
				while(maxTemp - minTemp > eps) {
					temp = (maxTemp + minTemp) * 0.5;
					rgb = temperature2rgb(temp);
					if((rgb[2] / rgb[0]) >= (b / r)) {
						maxTemp = temp;
					} else {
						minTemp = temp;
					}
				}
				return round(temp);
			};

			chroma.temperature = chroma.kelvin = function() {
				return(function(func, args, ctor) {
					ctor.prototype = func.prototype;
					var child = new ctor,
						result = func.apply(child, args);
					return Object(result) === result ? result : child;
				})(Color, slice.call(arguments).concat(['temperature']), function() {});
			};

			_input.temperature = _input.kelvin = _input.K = temperature2rgb;

			Color.prototype.temperature = function() {
				return rgb2temperature(this._rgb);
			};

			Color.prototype.kelvin = Color.prototype.temperature;

			chroma.contrast = function(a, b) {
				var l1, l2, ref, ref1;
				if((ref = type(a)) === 'string' || ref === 'number') {
					a = new Color(a);
				}
				if((ref1 = type(b)) === 'string' || ref1 === 'number') {
					b = new Color(b);
				}
				l1 = a.luminance();
				l2 = b.luminance();
				if(l1 > l2) {
					return(l1 + 0.05) / (l2 + 0.05);
				} else {
					return(l2 + 0.05) / (l1 + 0.05);
				}
			};

			Color.prototype.darken = function(amount) {
				var lch, me;
				if(amount == null) {
					amount = 20;
				}
				me = this;
				lch = me.lch();
				lch[0] -= amount;
				return chroma.lch(lch).alpha(me.alpha());
			};

			Color.prototype.darker = Color.prototype.darken;

			Color.prototype.brighten = function(amount) {
				if(amount == null) {
					amount = 20;
				}
				return this.darken(-amount);
			};

			Color.prototype.brighter = Color.prototype.brighten;

			Color.prototype.saturate = function(amount) {
				var lch, me;
				if(amount == null) {
					amount = 20;
				}
				me = this;
				lch = me.lch();
				lch[1] += amount;
				if(lch[1] < 0) {
					lch[1] = 0;
				}
				return chroma.lch(lch).alpha(me.alpha());
			};

			Color.prototype.desaturate = function(amount) {
				if(amount == null) {
					amount = 20;
				}
				return this.saturate(-amount);
			};

			Color.prototype.premultiply = function() {
				var a, rgb;
				rgb = this.rgb();
				a = this.alpha();
				return chroma(rgb[0] * a, rgb[1] * a, rgb[2] * a, a);
			};

			blend = function(bottom, top, mode) {
				if(!blend[mode]) {
					throw 'unknown blend mode ' + mode;
				}
				return blend[mode](bottom, top);
			};

			blend_f = function(f) {
				return function(bottom, top) {
					var c0, c1;
					c0 = chroma(top).rgb();
					c1 = chroma(bottom).rgb();
					return chroma(f(c0, c1), 'rgb');
				};
			};

			each = function(f) {
				return function(c0, c1) {
					var i, o, out;
					out = [];
					for(i = o = 0; o <= 3; i = ++o) {
						out[i] = f(c0[i], c1[i]);
					}
					return out;
				};
			};

			normal = function(a, b) {
				return a;
			};

			multiply = function(a, b) {
				return a * b / 255;
			};

			darken = function(a, b) {
				if(a > b) {
					return b;
				} else {
					return a;
				}
			};

			lighten = function(a, b) {
				if(a > b) {
					return a;
				} else {
					return b;
				}
			};

			screen = function(a, b) {
				return 255 * (1 - (1 - a / 255) * (1 - b / 255));
			};

			overlay = function(a, b) {
				if(b < 128) {
					return 2 * a * b / 255;
				} else {
					return 255 * (1 - 2 * (1 - a / 255) * (1 - b / 255));
				}
			};

			burn = function(a, b) {
				return 255 * (1 - (1 - b / 255) / (a / 255));
			};

			dodge = function(a, b) {
				if(a === 255) {
					return 255;
				}
				a = 255 * (b / 255) / (1 - a / 255);
				if(a > 255) {
					return 255;
				} else {
					return a;
				}
			};

			blend.normal = blend_f(each(normal));

			blend.multiply = blend_f(each(multiply));

			blend.screen = blend_f(each(screen));

			blend.overlay = blend_f(each(overlay));

			blend.darken = blend_f(each(darken));

			blend.lighten = blend_f(each(lighten));

			blend.dodge = blend_f(each(dodge));

			blend.burn = blend_f(each(burn));

			chroma.blend = blend;

			/*
			    chroma.js
  
			    Copyright (c) 2011-2013, Gregor Aisch
			    All rights reserved.
  
			    Redistribution and use in source and binary forms, with or without
			    modification, are permitted provided that the following conditions are met:
  
			    * Redistributions of source code must retain the above copyright notice, this
			      list of conditions and the following disclaimer.
  
			    * Redistributions in binary form must reproduce the above copyright notice,
			      this list of conditions and the following disclaimer in the documentation
			      and/or other materials provided with the distribution.
  
			    * The name Gregor Aisch may not be used to endorse or promote products
			      derived from this software without specific prior written permission.
  
			    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
			    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
			    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
			    DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
			    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
			    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
			    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
			    OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
			    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
			    EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
			    @source: https://github.com/gka/chroma.js
			 */

			chroma.scale = function(colors, positions) {
				var _colorCache, _colors, _correctLightness, _domain, _fixed, _max, _min, _mode, _nacol, _numClasses, _out, _pos, _spread, classifyValue, f, getClass, getColor, resetCache, setColors, setDomain, tmap;
				_mode = 'rgb';
				_nacol = chroma('#ccc');
				_spread = 0;
				_fixed = false;
				_domain = [0, 1];
				_colors = [];
				_out = false;
				_pos = [];
				_min = 0;
				_max = 1;
				_correctLightness = false;
				_numClasses = 0;
				_colorCache = {};
				setColors = function(colors, positions) {
					var c, col, o, ref, ref1, ref2, w;
					if(colors == null) {
						colors = ['#ddd', '#222'];
					}
					if((colors != null) && type(colors) === 'string' && (((ref = chroma.brewer) != null ? ref[colors] : void 0) != null)) {
						colors = chroma.brewer[colors];
					}
					if(type(colors) === 'array') {
						colors = colors.slice(0);
						for(c = o = 0, ref1 = colors.length - 1; 0 <= ref1 ? o <= ref1 : o >= ref1; c = 0 <= ref1 ? ++o : --o) {
							col = colors[c];
							if(type(col) === "string") {
								colors[c] = chroma(col);
							}
						}
						if(positions != null) {
							_pos = positions;
						} else {
							_pos = [];
							for(c = w = 0, ref2 = colors.length - 1; 0 <= ref2 ? w <= ref2 : w >= ref2; c = 0 <= ref2 ? ++w : --w) {
								_pos.push(c / (colors.length - 1));
							}
						}
					}
					resetCache();
					return _colors = colors;
				};
				setDomain = function(domain) {
					if(domain == null) {
						domain = [];
					}

					/*
					 * use this if you want to display a limited number of data classes
					 * possible methods are "equalinterval", "quantiles", "custom"
					 */
					_domain = domain;
					_min = domain[0];
					_max = domain[domain.length - 1];
					resetCache();
					if(domain.length === 2) {
						return _numClasses = 0;
					} else {
						return _numClasses = domain.length - 1;
					}
				};
				getClass = function(value) {
					var i, n;
					if(_domain != null) {
						n = _domain.length - 1;
						i = 0;
						while(i < n && value >= _domain[i]) {
							i++;
						}
						return i - 1;
					}
					return 0;
				};
				tmap = function(t) {
					return t;
				};
				classifyValue = function(value) {
					var i, maxc, minc, n, val;
					val = value;
					if(_domain.length > 2) {
						n = _domain.length - 1;
						i = getClass(value);
						minc = _domain[0] + (_domain[1] - _domain[0]) * (0 + _spread * 0.5);
						maxc = _domain[n - 1] + (_domain[n] - _domain[n - 1]) * (1 - _spread * 0.5);
						val = _min + ((_domain[i] + (_domain[i + 1] - _domain[i]) * 0.5 - minc) / (maxc - minc)) * (_max - _min);
					}
					return val;
				};
				getColor = function(val, bypassMap) {
					var c, col, f0, i, k, o, p, ref, t;
					if(bypassMap == null) {
						bypassMap = false;
					}
					if(isNaN(val)) {
						return _nacol;
					}
					if(!bypassMap) {
						if(_domain.length > 2) {
							c = getClass(val);
							t = c / (_numClasses - 1);
						} else {
							t = f0 = _min !== _max ? (val - _min) / (_max - _min) : 0;
							t = f0 = (val - _min) / (_max - _min);
							t = Math.min(1, Math.max(0, t));
						}
					} else {
						t = val;
					}
					if(!bypassMap) {
						t = tmap(t);
					}
					k = Math.floor(t * 10000);
					if(_colorCache[k]) {
						col = _colorCache[k];
					} else {
						if(type(_colors) === 'array') {
							for(i = o = 0, ref = _pos.length - 1; 0 <= ref ? o <= ref : o >= ref; i = 0 <= ref ? ++o : --o) {
								p = _pos[i];
								if(t <= p) {
									col = _colors[i];
									break;
								}
								if(t >= p && i === _pos.length - 1) {
									col = _colors[i];
									break;
								}
								if(t > p && t < _pos[i + 1]) {
									t = (t - p) / (_pos[i + 1] - p);
									col = chroma.interpolate(_colors[i], _colors[i + 1], t, _mode);
									break;
								}
							}
						} else if(type(_colors) === 'function') {
							col = _colors(t);
						}
						_colorCache[k] = col;
					}
					return col;
				};
				resetCache = function() {
					return _colorCache = {};
				};
				setColors(colors, positions);
				f = function(v) {
					var c;
					c = getColor(v);
					if(_out && c[_out]) {
						return c[_out]();
					} else {
						return c;
					}
				};
				f.domain = function(domain, classes, mode, key) {
					var d;
					if(mode == null) {
						mode = 'e';
					}
					if(!arguments.length) {
						return _domain;
					}
					if(classes != null) {
						d = chroma.analyze(domain, key);
						if(classes === 0) {
							domain = [d.min, d.max];
						} else {
							domain = chroma.limits(d, mode, classes);
						}
					}
					setDomain(domain);
					return f;
				};
				f.mode = function(_m) {
					if(!arguments.length) {
						return _mode;
					}
					_mode = _m;
					resetCache();
					return f;
				};
				f.range = function(colors, _pos) {
					setColors(colors, _pos);
					return f;
				};
				f.out = function(_o) {
					_out = _o;
					return f;
				};
				f.spread = function(val) {
					if(!arguments.length) {
						return _spread;
					}
					_spread = val;
					return f;
				};
				f.correctLightness = function(v) {
					if(!arguments.length) {
						return _correctLightness;
					}
					_correctLightness = v;
					resetCache();
					if(_correctLightness) {
						tmap = function(t) {
							var L0, L1, L_actual, L_diff, L_ideal, max_iter, pol, t0, t1;
							L0 = getColor(0, true).lab()[0];
							L1 = getColor(1, true).lab()[0];
							pol = L0 > L1;
							L_actual = getColor(t, true).lab()[0];
							L_ideal = L0 + (L1 - L0) * t;
							L_diff = L_actual - L_ideal;
							t0 = 0;
							t1 = 1;
							max_iter = 20;
							while(Math.abs(L_diff) > 1e-2 && max_iter-- > 0) {
								(function() {
									if(pol) {
										L_diff *= -1;
									}
									if(L_diff < 0) {
										t0 = t;
										t += (t1 - t) * 0.5;
									} else {
										t1 = t;
										t += (t0 - t) * 0.5;
									}
									L_actual = getColor(t, true).lab()[0];
									return L_diff = L_actual - L_ideal;
								})();
							}
							return t;
						};
					} else {
						tmap = function(t) {
							return t;
						};
					}
					return f;
				};
				f.colors = function() {
					var aa, i, len, numColors, o, out, ref, results, samples, w;
					numColors = 0;
					out = 'hex';
					if(arguments.length === 1) {
						if(type(arguments[0]) === 'string') {
							out = arguments[0];
						} else {
							numColors = arguments[0];
						}
					}
					if(arguments.length === 2) {
						numColors = arguments[0], out = arguments[1];
					}
					if(numColors) {
						return(function() {
							results = [];
							for(var o = 0; 0 <= numColors ? o < numColors : o > numColors; 0 <= numColors ? o++ : o--) {
								results.push(o);
							}
							return results;
						}).apply(this).map(function(i) {
							return f(i / (numColors - 1))[out]();
						});
					}
					colors = [];
					samples = [];
					if(_domain.length > 2) {
						for(i = w = 1, ref = _domain.length; 1 <= ref ? w < ref : w > ref; i = 1 <= ref ? ++w : --w) {
							samples.push((_domain[i - 1] + _domain[i]) * 0.5);
						}
					} else {
						samples = _domain;
					}
					for(aa = 0, len = samples.length; aa < len; aa++) {
						i = samples[aa];
						colors.push(f(i)[out]());
					}
					return colors;
				};
				return f;
			};

			if(chroma.scales == null) {
				chroma.scales = {};
			}

			chroma.scales.cool = function() {
				return chroma.scale([chroma.hsl(180, 1, .9), chroma.hsl(250, .7, .4)]);
			};

			chroma.scales.hot = function() {
				return chroma.scale(['#000', '#f00', '#ff0', '#fff'], [0, .25, .75, 1]).mode('rgb');
			};

			/*
			    chroma.js
  
			    Copyright (c) 2011-2013, Gregor Aisch
			    All rights reserved.
  
			    Redistribution and use in source and binary forms, with or without
			    modification, are permitted provided that the following conditions are met:
  
			    * Redistributions of source code must retain the above copyright notice, this
			      list of conditions and the following disclaimer.
  
			    * Redistributions in binary form must reproduce the above copyright notice,
			      this list of conditions and the following disclaimer in the documentation
			      and/or other materials provided with the distribution.
  
			    * The name Gregor Aisch may not be used to endorse or promote products
			      derived from this software without specific prior written permission.
  
			    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
			    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
			    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
			    DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
			    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
			    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
			    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
			    OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
			    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
			    EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
			    @source: https://github.com/gka/chroma.js
			 */

			chroma.analyze = function(data, key, filter) {
				var add, k, len, o, r, val, visit;
				r = {
					min: Number.MAX_VALUE,
					max: Number.MAX_VALUE * -1,
					sum: 0,
					values: [],
					count: 0
				};
				if(filter == null) {
					filter = function() {
						return true;
					};
				}
				add = function(val) {
					if((val != null) && !isNaN(val)) {
						r.values.push(val);
						r.sum += val;
						if(val < r.min) {
							r.min = val;
						}
						if(val > r.max) {
							r.max = val;
						}
						r.count += 1;
					}
				};
				visit = function(val, k) {
					if(filter(val, k)) {
						if((key != null) && type(key) === 'function') {
							return add(key(val));
						} else if((key != null) && type(key) === 'string' || type(key) === 'number') {
							return add(val[key]);
						} else {
							return add(val);
						}
					}
				};
				if(type(data) === 'array') {
					for(o = 0, len = data.length; o < len; o++) {
						val = data[o];
						visit(val);
					}
				} else {
					for(k in data) {
						val = data[k];
						visit(val, k);
					}
				}
				r.domain = [r.min, r.max];
				r.limits = function(mode, num) {
					return chroma.limits(r, mode, num);
				};
				return r;
			};

			chroma.limits = function(data, mode, num) {
				var aa, ab, ac, ad, ae, af, ag, ah, ai, aj, ak, al, am, assignments, best, centroids, cluster, clusterSizes, dist, i, j, kClusters, limits, max_log, min, min_log, mindist, n, nb_iters, newCentroids, o, p, pb, pr, ref, ref1, ref10, ref11, ref12, ref13, ref14, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, repeat, sum, tmpKMeansBreaks, value, values, w;
				if(mode == null) {
					mode = 'equal';
				}
				if(num == null) {
					num = 7;
				}
				if(type(data) === 'array') {
					data = chroma.analyze(data);
				}
				min = data.min;
				max = data.max;
				sum = data.sum;
				values = data.values.sort(function(a, b) {
					return a - b;
				});
				limits = [];
				if(mode.substr(0, 1) === 'c') {
					limits.push(min);
					limits.push(max);
				}
				if(mode.substr(0, 1) === 'e') {
					limits.push(min);
					for(i = o = 1, ref = num - 1; 1 <= ref ? o <= ref : o >= ref; i = 1 <= ref ? ++o : --o) {
						limits.push(min + (i / num) * (max - min));
					}
					limits.push(max);
				} else if(mode.substr(0, 1) === 'l') {
					if(min <= 0) {
						throw 'Logarithmic scales are only possible for values > 0';
					}
					min_log = Math.LOG10E * log(min);
					max_log = Math.LOG10E * log(max);
					limits.push(min);
					for(i = w = 1, ref1 = num - 1; 1 <= ref1 ? w <= ref1 : w >= ref1; i = 1 <= ref1 ? ++w : --w) {
						limits.push(pow(10, min_log + (i / num) * (max_log - min_log)));
					}
					limits.push(max);
				} else if(mode.substr(0, 1) === 'q') {
					limits.push(min);
					for(i = aa = 1, ref2 = num - 1; 1 <= ref2 ? aa <= ref2 : aa >= ref2; i = 1 <= ref2 ? ++aa : --aa) {
						p = values.length * i / num;
						pb = floor(p);
						if(pb === p) {
							limits.push(values[pb]);
						} else {
							pr = p - pb;
							limits.push(values[pb] * pr + values[pb + 1] * (1 - pr));
						}
					}
					limits.push(max);
				} else if(mode.substr(0, 1) === 'k') {

					/*
					implementation based on
					http://code.google.com/p/figue/source/browse/trunk/figue.js#336
					simplified for 1-d input values
					 */
					n = values.length;
					assignments = new Array(n);
					clusterSizes = new Array(num);
					repeat = true;
					nb_iters = 0;
					centroids = null;
					centroids = [];
					centroids.push(min);
					for(i = ab = 1, ref3 = num - 1; 1 <= ref3 ? ab <= ref3 : ab >= ref3; i = 1 <= ref3 ? ++ab : --ab) {
						centroids.push(min + (i / num) * (max - min));
					}
					centroids.push(max);
					while(repeat) {
						for(j = ac = 0, ref4 = num - 1; 0 <= ref4 ? ac <= ref4 : ac >= ref4; j = 0 <= ref4 ? ++ac : --ac) {
							clusterSizes[j] = 0;
						}
						for(i = ad = 0, ref5 = n - 1; 0 <= ref5 ? ad <= ref5 : ad >= ref5; i = 0 <= ref5 ? ++ad : --ad) {
							value = values[i];
							mindist = Number.MAX_VALUE;
							for(j = ae = 0, ref6 = num - 1; 0 <= ref6 ? ae <= ref6 : ae >= ref6; j = 0 <= ref6 ? ++ae : --ae) {
								dist = abs(centroids[j] - value);
								if(dist < mindist) {
									mindist = dist;
									best = j;
								}
							}
							clusterSizes[best]++;
							assignments[i] = best;
						}
						newCentroids = new Array(num);
						for(j = af = 0, ref7 = num - 1; 0 <= ref7 ? af <= ref7 : af >= ref7; j = 0 <= ref7 ? ++af : --af) {
							newCentroids[j] = null;
						}
						for(i = ag = 0, ref8 = n - 1; 0 <= ref8 ? ag <= ref8 : ag >= ref8; i = 0 <= ref8 ? ++ag : --ag) {
							cluster = assignments[i];
							if(newCentroids[cluster] === null) {
								newCentroids[cluster] = values[i];
							} else {
								newCentroids[cluster] += values[i];
							}
						}
						for(j = ah = 0, ref9 = num - 1; 0 <= ref9 ? ah <= ref9 : ah >= ref9; j = 0 <= ref9 ? ++ah : --ah) {
							newCentroids[j] *= 1 / clusterSizes[j];
						}
						repeat = false;
						for(j = ai = 0, ref10 = num - 1; 0 <= ref10 ? ai <= ref10 : ai >= ref10; j = 0 <= ref10 ? ++ai : --ai) {
							if(newCentroids[j] !== centroids[i]) {
								repeat = true;
								break;
							}
						}
						centroids = newCentroids;
						nb_iters++;
						if(nb_iters > 200) {
							repeat = false;
						}
					}
					kClusters = {};
					for(j = aj = 0, ref11 = num - 1; 0 <= ref11 ? aj <= ref11 : aj >= ref11; j = 0 <= ref11 ? ++aj : --aj) {
						kClusters[j] = [];
					}
					for(i = ak = 0, ref12 = n - 1; 0 <= ref12 ? ak <= ref12 : ak >= ref12; i = 0 <= ref12 ? ++ak : --ak) {
						cluster = assignments[i];
						kClusters[cluster].push(values[i]);
					}
					tmpKMeansBreaks = [];
					for(j = al = 0, ref13 = num - 1; 0 <= ref13 ? al <= ref13 : al >= ref13; j = 0 <= ref13 ? ++al : --al) {
						tmpKMeansBreaks.push(kClusters[j][0]);
						tmpKMeansBreaks.push(kClusters[j][kClusters[j].length - 1]);
					}
					tmpKMeansBreaks = tmpKMeansBreaks.sort(function(a, b) {
						return a - b;
					});
					limits.push(tmpKMeansBreaks[0]);
					for(i = am = 1, ref14 = tmpKMeansBreaks.length - 1; am <= ref14; i = am += 2) {
						if(!isNaN(tmpKMeansBreaks[i])) {
							limits.push(tmpKMeansBreaks[i]);
						}
					}
				}
				return limits;
			};

			hsi2rgb = function(h, s, i) {

				/*
				borrowed from here:
				http://hummer.stanford.edu/museinfo/doc/examples/humdrum/keyscape2/hsi2rgb.cpp
				 */
				var b, g, r, ref;
				ref = unpack(arguments), h = ref[0], s = ref[1], i = ref[2];
				h /= 360;
				if(h < 1 / 3) {
					b = (1 - s) / 3;
					r = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3;
					g = 1 - (b + r);
				} else if(h < 2 / 3) {
					h -= 1 / 3;
					r = (1 - s) / 3;
					g = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3;
					b = 1 - (r + g);
				} else {
					h -= 2 / 3;
					g = (1 - s) / 3;
					b = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3;
					r = 1 - (g + b);
				}
				r = limit(i * r * 3);
				g = limit(i * g * 3);
				b = limit(i * b * 3);
				return [r * 255, g * 255, b * 255];
			};

			rgb2hsi = function() {

				/*
				borrowed from here:
				http://hummer.stanford.edu/museinfo/doc/examples/humdrum/keyscape2/rgb2hsi.cpp
				 */
				var b, g, h, i, min, r, ref, s;
				ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
				TWOPI = Math.PI * 2;
				r /= 255;
				g /= 255;
				b /= 255;
				min = Math.min(r, g, b);
				i = (r + g + b) / 3;
				s = 1 - min / i;
				if(s === 0) {
					h = 0;
				} else {
					h = ((r - g) + (r - b)) / 2;
					h /= Math.sqrt((r - g) * (r - g) + (r - b) * (g - b));
					h = Math.acos(h);
					if(b > g) {
						h = TWOPI - h;
					}
					h /= TWOPI;
				}
				return [h * 360, s, i];
			};

			chroma.hsi = function() {
				return(function(func, args, ctor) {
					ctor.prototype = func.prototype;
					var child = new ctor,
						result = func.apply(child, args);
					return Object(result) === result ? result : child;
				})(Color, slice.call(arguments).concat(['hsi']), function() {});
			};

			_input.hsi = hsi2rgb;

			Color.prototype.hsi = function() {
				return rgb2hsi(this._rgb);
			};

			interpolate_hsx = function(col1, col2, f, m) {
				var dh, hue, hue0, hue1, lbv, lbv0, lbv1, res, sat, sat0, sat1, xyz0, xyz1;
				if(m === 'hsl') {
					xyz0 = col1.hsl();
					xyz1 = col2.hsl();
				} else if(m === 'hsv') {
					xyz0 = col1.hsv();
					xyz1 = col2.hsv();
				} else if(m === 'hsi') {
					xyz0 = col1.hsi();
					xyz1 = col2.hsi();
				} else if(m === 'lch') {
					xyz0 = col1.lch();
					xyz1 = col2.lch();
				}
				if(m.substr(0, 1) === 'h') {
					hue0 = xyz0[0], sat0 = xyz0[1], lbv0 = xyz0[2];
					hue1 = xyz1[0], sat1 = xyz1[1], lbv1 = xyz1[2];
				} else {
					lbv0 = xyz0[0], sat0 = xyz0[1], hue0 = xyz0[2];
					lbv1 = xyz1[0], sat1 = xyz1[1], hue1 = xyz1[2];
				}
				if(!isNaN(hue0) && !isNaN(hue1)) {
					if(hue1 > hue0 && hue1 - hue0 > 180) {
						dh = hue1 - (hue0 + 360);
					} else if(hue1 < hue0 && hue0 - hue1 > 180) {
						dh = hue1 + 360 - hue0;
					} else {
						dh = hue1 - hue0;
					}
					hue = hue0 + f * dh;
				} else if(!isNaN(hue0)) {
					hue = hue0;
					if((lbv1 === 1 || lbv1 === 0) && m !== 'hsv') {
						sat = sat0;
					}
				} else if(!isNaN(hue1)) {
					hue = hue1;
					if((lbv0 === 1 || lbv0 === 0) && m !== 'hsv') {
						sat = sat1;
					}
				} else {
					hue = Number.NaN;
				}
				if(sat == null) {
					sat = sat0 + f * (sat1 - sat0);
				}
				lbv = lbv0 + f * (lbv1 - lbv0);
				if(m.substr(0, 1) === 'h') {
					return res = new Color(hue, sat, lbv, m);
				} else {
					return res = new Color(lbv, sat, hue, m);
				}
			};

			_interpolators = _interpolators.concat((function() {
				var len, o, ref, results;
				ref = ['hsv', 'hsl', 'hsi', 'lch'];
				results = [];
				for(o = 0, len = ref.length; o < len; o++) {
					m = ref[o];
					results.push([m, interpolate_hsx]);
				}
				return results;
			})());

			interpolate_num = function(col1, col2, f, m) {
				var n1, n2;
				n1 = col1.num();
				n2 = col2.num();
				return chroma.num(n1 + (n2 - n1) * f, 'num');
			};

			_interpolators.push(['num', interpolate_num]);

			interpolate_lab = function(col1, col2, f, m) {
				var res, xyz0, xyz1;
				xyz0 = col1.lab();
				xyz1 = col2.lab();
				return res = new Color(xyz0[0] + f * (xyz1[0] - xyz0[0]), xyz0[1] + f * (xyz1[1] - xyz0[1]), xyz0[2] + f * (xyz1[2] - xyz0[2]), m);
			};

			_interpolators.push(['lab', interpolate_lab]);

		}).call(this);

	}, {}],
	3: [function(require, module, exports) {
		! function() {
			var d3 = {
				version: "3.5.6"
			};
			var d3_arraySlice = [].slice,
				d3_array = function(list) {
					return d3_arraySlice.call(list);
				};
			var d3_document = this.document;

			function d3_documentElement(node) {
				return node && (node.ownerDocument || node.document || node).documentElement;
			}

			function d3_window(node) {
				return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView);
			}
			if(d3_document) {
				try {
					d3_array(d3_document.documentElement.childNodes)[0].nodeType;
				} catch(e) {
					d3_array = function(list) {
						var i = list.length,
							array = new Array(i);
						while(i--) array[i] = list[i];
						return array;
					};
				}
			}
			if(!Date.now) Date.now = function() {
				return +new Date();
			};
			if(d3_document) {
				try {
					d3_document.createElement("DIV").style.setProperty("opacity", 0, "");
				} catch(error) {
					var d3_element_prototype = this.Element.prototype,
						d3_element_setAttribute = d3_element_prototype.setAttribute,
						d3_element_setAttributeNS = d3_element_prototype.setAttributeNS,
						d3_style_prototype = this.CSSStyleDeclaration.prototype,
						d3_style_setProperty = d3_style_prototype.setProperty;
					d3_element_prototype.setAttribute = function(name, value) {
						d3_element_setAttribute.call(this, name, value + "");
					};
					d3_element_prototype.setAttributeNS = function(space, local, value) {
						d3_element_setAttributeNS.call(this, space, local, value + "");
					};
					d3_style_prototype.setProperty = function(name, value, priority) {
						d3_style_setProperty.call(this, name, value + "", priority);
					};
				}
			}
			d3.ascending = d3_ascending;

			function d3_ascending(a, b) {
				return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
			}
			d3.descending = function(a, b) {
				return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
			};
			d3.min = function(array, f) {
				var i = -1,
					n = array.length,
					a, b;
				if(arguments.length === 1) {
					while(++i < n)
						if((b = array[i]) != null && b >= b) {
							a = b;
							break;
						}
					while(++i < n)
						if((b = array[i]) != null && a > b) a = b;
				} else {
					while(++i < n)
						if((b = f.call(array, array[i], i)) != null && b >= b) {
							a = b;
							break;
						}
					while(++i < n)
						if((b = f.call(array, array[i], i)) != null && a > b) a = b;
				}
				return a;
			};
			d3.max = function(array, f) {
				var i = -1,
					n = array.length,
					a, b;
				if(arguments.length === 1) {
					while(++i < n)
						if((b = array[i]) != null && b >= b) {
							a = b;
							break;
						}
					while(++i < n)
						if((b = array[i]) != null && b > a) a = b;
				} else {
					while(++i < n)
						if((b = f.call(array, array[i], i)) != null && b >= b) {
							a = b;
							break;
						}
					while(++i < n)
						if((b = f.call(array, array[i], i)) != null && b > a) a = b;
				}
				return a;
			};
			d3.extent = function(array, f) {
				var i = -1,
					n = array.length,
					a, b, c;
				if(arguments.length === 1) {
					while(++i < n)
						if((b = array[i]) != null && b >= b) {
							a = c = b;
							break;
						}
					while(++i < n)
						if((b = array[i]) != null) {
							if(a > b) a = b;
							if(c < b) c = b;
						}
				} else {
					while(++i < n)
						if((b = f.call(array, array[i], i)) != null && b >= b) {
							a = c = b;
							break;
						}
					while(++i < n)
						if((b = f.call(array, array[i], i)) != null) {
							if(a > b) a = b;
							if(c < b) c = b;
						}
				}
				return [a, c];
			};

			function d3_number(x) {
				return x === null ? NaN : +x;
			}

			function d3_numeric(x) {
				return !isNaN(x);
			}
			d3.sum = function(array, f) {
				var s = 0,
					n = array.length,
					a, i = -1;
				if(arguments.length === 1) {
					while(++i < n)
						if(d3_numeric(a = +array[i])) s += a;
				} else {
					while(++i < n)
						if(d3_numeric(a = +f.call(array, array[i], i))) s += a;
				}
				return s;
			};
			d3.mean = function(array, f) {
				var s = 0,
					n = array.length,
					a, i = -1,
					j = n;
				if(arguments.length === 1) {
					while(++i < n)
						if(d3_numeric(a = d3_number(array[i]))) s += a;
						else --j;
				} else {
					while(++i < n)
						if(d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a;
						else --j;
				}
				if(j) return s / j;
			};
			d3.quantile = function(values, p) {
				var H = (values.length - 1) * p + 1,
					h = Math.floor(H),
					v = +values[h - 1],
					e = H - h;
				return e ? v + e * (values[h] - v) : v;
			};
			d3.median = function(array, f) {
				var numbers = [],
					n = array.length,
					a, i = -1;
				if(arguments.length === 1) {
					while(++i < n)
						if(d3_numeric(a = d3_number(array[i]))) numbers.push(a);
				} else {
					while(++i < n)
						if(d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a);
				}
				if(numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5);
			};
			d3.variance = function(array, f) {
				var n = array.length,
					m = 0,
					a, d, s = 0,
					i = -1,
					j = 0;
				if(arguments.length === 1) {
					while(++i < n) {
						if(d3_numeric(a = d3_number(array[i]))) {
							d = a - m;
							m += d / ++j;
							s += d * (a - m);
						}
					}
				} else {
					while(++i < n) {
						if(d3_numeric(a = d3_number(f.call(array, array[i], i)))) {
							d = a - m;
							m += d / ++j;
							s += d * (a - m);
						}
					}
				}
				if(j > 1) return s / (j - 1);
			};
			d3.deviation = function() {
				var v = d3.variance.apply(this, arguments);
				return v ? Math.sqrt(v) : v;
			};

			function d3_bisector(compare) {
				return {
					left: function(a, x, lo, hi) {
						if(arguments.length < 3) lo = 0;
						if(arguments.length < 4) hi = a.length;
						while(lo < hi) {
							var mid = lo + hi >>> 1;
							if(compare(a[mid], x) < 0) lo = mid + 1;
							else hi = mid;
						}
						return lo;
					},
					right: function(a, x, lo, hi) {
						if(arguments.length < 3) lo = 0;
						if(arguments.length < 4) hi = a.length;
						while(lo < hi) {
							var mid = lo + hi >>> 1;
							if(compare(a[mid], x) > 0) hi = mid;
							else lo = mid + 1;
						}
						return lo;
					}
				};
			}
			var d3_bisect = d3_bisector(d3_ascending);
			d3.bisectLeft = d3_bisect.left;
			d3.bisect = d3.bisectRight = d3_bisect.right;
			d3.bisector = function(f) {
				return d3_bisector(f.length === 1 ? function(d, x) {
					return d3_ascending(f(d), x);
				} : f);
			};
			d3.shuffle = function(array, i0, i1) {
				if((m = arguments.length) < 3) {
					i1 = array.length;
					if(m < 2) i0 = 0;
				}
				var m = i1 - i0,
					t, i;
				while(m) {
					i = Math.random() * m-- | 0;
					t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t;
				}
				return array;
			};
			d3.permute = function(array, indexes) {
				var i = indexes.length,
					permutes = new Array(i);
				while(i--) permutes[i] = array[indexes[i]];
				return permutes;
			};
			d3.pairs = function(array) {
				var i = 0,
					n = array.length - 1,
					p0, p1 = array[0],
					pairs = new Array(n < 0 ? 0 : n);
				while(i < n) pairs[i] = [p0 = p1, p1 = array[++i]];
				return pairs;
			};
			d3.zip = function() {
				if(!(n = arguments.length)) return [];
				for(var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m;) {
					for(var j = -1, n, zip = zips[i] = new Array(n); ++j < n;) {
						zip[j] = arguments[j][i];
					}
				}
				return zips;
			};

			function d3_zipLength(d) {
				return d.length;
			}
			d3.transpose = function(matrix) {
				return d3.zip.apply(d3, matrix);
			};
			d3.keys = function(map) {
				var keys = [];
				for(var key in map) keys.push(key);
				return keys;
			};
			d3.values = function(map) {
				var values = [];
				for(var key in map) values.push(map[key]);
				return values;
			};
			d3.entries = function(map) {
				var entries = [];
				for(var key in map) entries.push({
					key: key,
					value: map[key]
				});
				return entries;
			};
			d3.merge = function(arrays) {
				var n = arrays.length,
					m, i = -1,
					j = 0,
					merged, array;
				while(++i < n) j += arrays[i].length;
				merged = new Array(j);
				while(--n >= 0) {
					array = arrays[n];
					m = array.length;
					while(--m >= 0) {
						merged[--j] = array[m];
					}
				}
				return merged;
			};
			var abs = Math.abs;
			d3.range = function(start, stop, step) {
				if(arguments.length < 3) {
					step = 1;
					if(arguments.length < 2) {
						stop = start;
						start = 0;
					}
				}
				if((stop - start) / step === Infinity) throw new Error("infinite range");
				var range = [],
					k = d3_range_integerScale(abs(step)),
					i = -1,
					j;
				start *= k, stop *= k, step *= k;
				if(step < 0)
					while((j = start + step * ++i) > stop) range.push(j / k);
				else
					while((j = start + step * ++i) < stop) range.push(j / k);
				return range;
			};

			function d3_range_integerScale(x) {
				var k = 1;
				while(x * k % 1) k *= 10;
				return k;
			}

			function d3_class(ctor, properties) {
				for(var key in properties) {
					Object.defineProperty(ctor.prototype, key, {
						value: properties[key],
						enumerable: false
					});
				}
			}
			d3.map = function(object, f) {
				var map = new d3_Map();
				if(object instanceof d3_Map) {
					object.forEach(function(key, value) {
						map.set(key, value);
					});
				} else if(Array.isArray(object)) {
					var i = -1,
						n = object.length,
						o;
					if(arguments.length === 1)
						while(++i < n) map.set(i, object[i]);
					else
						while(++i < n) map.set(f.call(object, o = object[i], i), o);
				} else {
					for(var key in object) map.set(key, object[key]);
				}
				return map;
			};

			function d3_Map() {
				this._ = Object.create(null);
			}
			var d3_map_proto = "__proto__",
				d3_map_zero = "\x00";
			d3_class(d3_Map, {
				has: d3_map_has,
				get: function(key) {
					return this._[d3_map_escape(key)];
				},
				set: function(key, value) {
					return this._[d3_map_escape(key)] = value;
				},
				remove: d3_map_remove,
				keys: d3_map_keys,
				values: function() {
					var values = [];
					for(var key in this._) values.push(this._[key]);
					return values;
				},
				entries: function() {
					var entries = [];
					for(var key in this._) entries.push({
						key: d3_map_unescape(key),
						value: this._[key]
					});
					return entries;
				},
				size: d3_map_size,
				empty: d3_map_empty,
				forEach: function(f) {
					for(var key in this._) f.call(this, d3_map_unescape(key), this._[key]);
				}
			});

			function d3_map_escape(key) {
				return(key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key;
			}

			function d3_map_unescape(key) {
				return(key += "")[0] === d3_map_zero ? key.slice(1) : key;
			}

			function d3_map_has(key) {
				return d3_map_escape(key) in this._;
			}

			function d3_map_remove(key) {
				return(key = d3_map_escape(key)) in this._ && delete this._[key];
			}

			function d3_map_keys() {
				var keys = [];
				for(var key in this._) keys.push(d3_map_unescape(key));
				return keys;
			}

			function d3_map_size() {
				var size = 0;
				for(var key in this._) ++size;
				return size;
			}

			function d3_map_empty() {
				for(var key in this._) return false;
				return true;
			}
			d3.nest = function() {
				var nest = {},
					keys = [],
					sortKeys = [],
					sortValues, rollup;

				function map(mapType, array, depth) {
					if(depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array;
					var i = -1,
						n = array.length,
						key = keys[depth++],
						keyValue, object, setter, valuesByKey = new d3_Map(),
						values;
					while(++i < n) {
						if(values = valuesByKey.get(keyValue = key(object = array[i]))) {
							values.push(object);
						} else {
							valuesByKey.set(keyValue, [object]);
						}
					}
					if(mapType) {
						object = mapType();
						setter = function(keyValue, values) {
							object.set(keyValue, map(mapType, values, depth));
						};
					} else {
						object = {};
						setter = function(keyValue, values) {
							object[keyValue] = map(mapType, values, depth);
						};
					}
					valuesByKey.forEach(setter);
					return object;
				}

				function entries(map, depth) {
					if(depth >= keys.length) return map;
					var array = [],
						sortKey = sortKeys[depth++];
					map.forEach(function(key, keyMap) {
						array.push({
							key: key,
							values: entries(keyMap, depth)
						});
					});
					return sortKey ? array.sort(function(a, b) {
						return sortKey(a.key, b.key);
					}) : array;
				}
				nest.map = function(array, mapType) {
					return map(mapType, array, 0);
				};
				nest.entries = function(array) {
					return entries(map(d3.map, array, 0), 0);
				};
				nest.key = function(d) {
					keys.push(d);
					return nest;
				};
				nest.sortKeys = function(order) {
					sortKeys[keys.length - 1] = order;
					return nest;
				};
				nest.sortValues = function(order) {
					sortValues = order;
					return nest;
				};
				nest.rollup = function(f) {
					rollup = f;
					return nest;
				};
				return nest;
			};
			d3.set = function(array) {
				var set = new d3_Set();
				if(array)
					for(var i = 0, n = array.length; i < n; ++i) set.add(array[i]);
				return set;
			};

			function d3_Set() {
				this._ = Object.create(null);
			}
			d3_class(d3_Set, {
				has: d3_map_has,
				add: function(key) {
					this._[d3_map_escape(key += "")] = true;
					return key;
				},
				remove: d3_map_remove,
				values: d3_map_keys,
				size: d3_map_size,
				empty: d3_map_empty,
				forEach: function(f) {
					for(var key in this._) f.call(this, d3_map_unescape(key));
				}
			});
			d3.behavior = {};

			function d3_identity(d) {
				return d;
			}
			d3.rebind = function(target, source) {
				var i = 1,
					n = arguments.length,
					method;
				while(++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
				return target;
			};

			function d3_rebind(target, source, method) {
				return function() {
					var value = method.apply(source, arguments);
					return value === source ? target : value;
				};
			}

			function d3_vendorSymbol(object, name) {
				if(name in object) return name;
				name = name.charAt(0).toUpperCase() + name.slice(1);
				for(var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) {
					var prefixName = d3_vendorPrefixes[i] + name;
					if(prefixName in object) return prefixName;
				}
			}
			var d3_vendorPrefixes = ["webkit", "ms", "moz", "Moz", "o", "O"];

			function d3_noop() {}
			d3.dispatch = function() {
				var dispatch = new d3_dispatch(),
					i = -1,
					n = arguments.length;
				while(++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
				return dispatch;
			};

			function d3_dispatch() {}
			d3_dispatch.prototype.on = function(type, listener) {
				var i = type.indexOf("."),
					name = "";
				if(i >= 0) {
					name = type.slice(i + 1);
					type = type.slice(0, i);
				}
				if(type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener);
				if(arguments.length === 2) {
					if(listener == null)
						for(type in this) {
							if(this.hasOwnProperty(type)) this[type].on(name, null);
						}
					return this;
				}
			};

			function d3_dispatch_event(dispatch) {
				var listeners = [],
					listenerByName = new d3_Map();

				function event() {
					var z = listeners,
						i = -1,
						n = z.length,
						l;
					while(++i < n)
						if(l = z[i].on) l.apply(this, arguments);
					return dispatch;
				}
				event.on = function(name, listener) {
					var l = listenerByName.get(name),
						i;
					if(arguments.length < 2) return l && l.on;
					if(l) {
						l.on = null;
						listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1));
						listenerByName.remove(name);
					}
					if(listener) listeners.push(listenerByName.set(name, {
						on: listener
					}));
					return dispatch;
				};
				return event;
			}
			d3.event = null;

			function d3_eventPreventDefault() {
				d3.event.preventDefault();
			}

			function d3_eventSource() {
				var e = d3.event,
					s;
				while(s = e.sourceEvent) e = s;
				return e;
			}

			function d3_eventDispatch(target) {
				var dispatch = new d3_dispatch(),
					i = 0,
					n = arguments.length;
				while(++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
				dispatch.of = function(thiz, argumentz) {
					return function(e1) {
						try {
							var e0 = e1.sourceEvent = d3.event;
							e1.target = target;
							d3.event = e1;
							dispatch[e1.type].apply(thiz, argumentz);
						} finally {
							d3.event = e0;
						}
					};
				};
				return dispatch;
			}
			d3.requote = function(s) {
				return s.replace(d3_requote_re, "\\$&");
			};
			var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
			var d3_subclass = {}.__proto__ ? function(object, prototype) {
				object.__proto__ = prototype;
			} : function(object, prototype) {
				for(var property in prototype) object[property] = prototype[property];
			};

			function d3_selection(groups) {
				d3_subclass(groups, d3_selectionPrototype);
				return groups;
			}
			var d3_select = function(s, n) {
					return n.querySelector(s);
				},
				d3_selectAll = function(s, n) {
					return n.querySelectorAll(s);
				},
				d3_selectMatches = function(n, s) {
					var d3_selectMatcher = n.matches || n[d3_vendorSymbol(n, "matchesSelector")];
					d3_selectMatches = function(n, s) {
						return d3_selectMatcher.call(n, s);
					};
					return d3_selectMatches(n, s);
				};
			if(typeof Sizzle === "function") {
				d3_select = function(s, n) {
					return Sizzle(s, n)[0] || null;
				};
				d3_selectAll = Sizzle;
				d3_selectMatches = Sizzle.matchesSelector;
			}
			d3.selection = function() {
				return d3.select(d3_document.documentElement);
			};
			var d3_selectionPrototype = d3.selection.prototype = [];
			d3_selectionPrototype.select = function(selector) {
				var subgroups = [],
					subgroup, subnode, group, node;
				selector = d3_selection_selector(selector);
				for(var j = -1, m = this.length; ++j < m;) {
					subgroups.push(subgroup = []);
					subgroup.parentNode = (group = this[j]).parentNode;
					for(var i = -1, n = group.length; ++i < n;) {
						if(node = group[i]) {
							subgroup.push(subnode = selector.call(node, node.__data__, i, j));
							if(subnode && "__data__" in node) subnode.__data__ = node.__data__;
						} else {
							subgroup.push(null);
						}
					}
				}
				return d3_selection(subgroups);
			};

			function d3_selection_selector(selector) {
				return typeof selector === "function" ? selector : function() {
					return d3_select(selector, this);
				};
			}
			d3_selectionPrototype.selectAll = function(selector) {
				var subgroups = [],
					subgroup, node;
				selector = d3_selection_selectorAll(selector);
				for(var j = -1, m = this.length; ++j < m;) {
					for(var group = this[j], i = -1, n = group.length; ++i < n;) {
						if(node = group[i]) {
							subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j)));
							subgroup.parentNode = node;
						}
					}
				}
				return d3_selection(subgroups);
			};

			function d3_selection_selectorAll(selector) {
				return typeof selector === "function" ? selector : function() {
					return d3_selectAll(selector, this);
				};
			}
			var d3_nsPrefix = {
				svg: "http://www.w3.org/2000/svg",
				xhtml: "http://www.w3.org/1999/xhtml",
				xlink: "http://www.w3.org/1999/xlink",
				xml: "http://www.w3.org/XML/1998/namespace",
				xmlns: "http://www.w3.org/2000/xmlns/"
			};
			d3.ns = {
				prefix: d3_nsPrefix,
				qualify: function(name) {
					var i = name.indexOf(":"),
						prefix = name;
					if(i >= 0) {
						prefix = name.slice(0, i);
						name = name.slice(i + 1);
					}
					return d3_nsPrefix.hasOwnProperty(prefix) ? {
						space: d3_nsPrefix[prefix],
						local: name
					} : name;
				}
			};
			d3_selectionPrototype.attr = function(name, value) {
				if(arguments.length < 2) {
					if(typeof name === "string") {
						var node = this.node();
						name = d3.ns.qualify(name);
						return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name);
					}
					for(value in name) this.each(d3_selection_attr(value, name[value]));
					return this;
				}
				return this.each(d3_selection_attr(name, value));
			};

			function d3_selection_attr(name, value) {
				name = d3.ns.qualify(name);

				function attrNull() {
					this.removeAttribute(name);
				}

				function attrNullNS() {
					this.removeAttributeNS(name.space, name.local);
				}

				function attrConstant() {
					this.setAttribute(name, value);
				}

				function attrConstantNS() {
					this.setAttributeNS(name.space, name.local, value);
				}

				function attrFunction() {
					var x = value.apply(this, arguments);
					if(x == null) this.removeAttribute(name);
					else this.setAttribute(name, x);
				}

				function attrFunctionNS() {
					var x = value.apply(this, arguments);
					if(x == null) this.removeAttributeNS(name.space, name.local);
					else this.setAttributeNS(name.space, name.local, x);
				}
				return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant;
			}

			function d3_collapse(s) {
				return s.trim().replace(/\s+/g, " ");
			}
			d3_selectionPrototype.classed = function(name, value) {
				if(arguments.length < 2) {
					if(typeof name === "string") {
						var node = this.node(),
							n = (name = d3_selection_classes(name)).length,
							i = -1;
						if(value = node.classList) {
							while(++i < n)
								if(!value.contains(name[i])) return false;
						} else {
							value = node.getAttribute("class");
							while(++i < n)
								if(!d3_selection_classedRe(name[i]).test(value)) return false;
						}
						return true;
					}
					for(value in name) this.each(d3_selection_classed(value, name[value]));
					return this;
				}
				return this.each(d3_selection_classed(name, value));
			};

			function d3_selection_classedRe(name) {
				return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
			}

			function d3_selection_classes(name) {
				return(name + "").trim().split(/^|\s+/);
			}

			function d3_selection_classed(name, value) {
				name = d3_selection_classes(name).map(d3_selection_classedName);
				var n = name.length;

				function classedConstant() {
					var i = -1;
					while(++i < n) name[i](this, value);
				}

				function classedFunction() {
					var i = -1,
						x = value.apply(this, arguments);
					while(++i < n) name[i](this, x);
				}
				return typeof value === "function" ? classedFunction : classedConstant;
			}

			function d3_selection_classedName(name) {
				var re = d3_selection_classedRe(name);
				return function(node, value) {
					if(c = node.classList) return value ? c.add(name) : c.remove(name);
					var c = node.getAttribute("class") || "";
					if(value) {
						re.lastIndex = 0;
						if(!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name));
					} else {
						node.setAttribute("class", d3_collapse(c.replace(re, " ")));
					}
				};
			}
			d3_selectionPrototype.style = function(name, value, priority) {
				var n = arguments.length;
				if(n < 3) {
					if(typeof name !== "string") {
						if(n < 2) value = "";
						for(priority in name) this.each(d3_selection_style(priority, name[priority], value));
						return this;
					}
					if(n < 2) {
						var node = this.node();
						return d3_window(node).getComputedStyle(node, null).getPropertyValue(name);
					}
					priority = "";
				}
				return this.each(d3_selection_style(name, value, priority));
			};

			function d3_selection_style(name, value, priority) {
				function styleNull() {
					this.style.removeProperty(name);
				}

				function styleConstant() {
					this.style.setProperty(name, value, priority);
				}

				function styleFunction() {
					var x = value.apply(this, arguments);
					if(x == null) this.style.removeProperty(name);
					else this.style.setProperty(name, x, priority);
				}
				return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant;
			}
			d3_selectionPrototype.property = function(name, value) {
				if(arguments.length < 2) {
					if(typeof name === "string") return this.node()[name];
					for(value in name) this.each(d3_selection_property(value, name[value]));
					return this;
				}
				return this.each(d3_selection_property(name, value));
			};

			function d3_selection_property(name, value) {
				function propertyNull() {
					delete this[name];
				}

				function propertyConstant() {
					this[name] = value;
				}

				function propertyFunction() {
					var x = value.apply(this, arguments);
					if(x == null) delete this[name];
					else this[name] = x;
				}
				return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant;
			}
			d3_selectionPrototype.text = function(value) {
				return arguments.length ? this.each(typeof value === "function" ? function() {
					var v = value.apply(this, arguments);
					this.textContent = v == null ? "" : v;
				} : value == null ? function() {
					this.textContent = "";
				} : function() {
					this.textContent = value;
				}) : this.node().textContent;
			};
			d3_selectionPrototype.html = function(value) {
				return arguments.length ? this.each(typeof value === "function" ? function() {
					var v = value.apply(this, arguments);
					this.innerHTML = v == null ? "" : v;
				} : value == null ? function() {
					this.innerHTML = "";
				} : function() {
					this.innerHTML = value;
				}) : this.node().innerHTML;
			};
			d3_selectionPrototype.append = function(name) {
				name = d3_selection_creator(name);
				return this.select(function() {
					return this.appendChild(name.apply(this, arguments));
				});
			};

			function d3_selection_creator(name) {
				function create() {
					var document = this.ownerDocument,
						namespace = this.namespaceURI;
					return namespace ? document.createElementNS(namespace, name) : document.createElement(name);
				}

				function createNS() {
					return this.ownerDocument.createElementNS(name.space, name.local);
				}
				return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create;
			}
			d3_selectionPrototype.insert = function(name, before) {
				name = d3_selection_creator(name);
				before = d3_selection_selector(before);
				return this.select(function() {
					return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null);
				});
			};
			d3_selectionPrototype.remove = function() {
				return this.each(d3_selectionRemove);
			};

			function d3_selectionRemove() {
				var parent = this.parentNode;
				if(parent) parent.removeChild(this);
			}
			d3_selectionPrototype.data = function(value, key) {
				var i = -1,
					n = this.length,
					group, node;
				if(!arguments.length) {
					value = new Array(n = (group = this[0]).length);
					while(++i < n) {
						if(node = group[i]) {
							value[i] = node.__data__;
						}
					}
					return value;
				}

				function bind(group, groupData) {
					var i, n = group.length,
						m = groupData.length,
						n0 = Math.min(n, m),
						updateNodes = new Array(m),
						enterNodes = new Array(m),
						exitNodes = new Array(n),
						node, nodeData;
					if(key) {
						var nodeByKeyValue = new d3_Map(),
							keyValues = new Array(n),
							keyValue;
						for(i = -1; ++i < n;) {
							if(nodeByKeyValue.has(keyValue = key.call(node = group[i], node.__data__, i))) {
								exitNodes[i] = node;
							} else {
								nodeByKeyValue.set(keyValue, node);
							}
							keyValues[i] = keyValue;
						}
						for(i = -1; ++i < m;) {
							if(!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) {
								enterNodes[i] = d3_selection_dataNode(nodeData);
							} else if(node !== true) {
								updateNodes[i] = node;
								node.__data__ = nodeData;
							}
							nodeByKeyValue.set(keyValue, true);
						}
						for(i = -1; ++i < n;) {
							if(nodeByKeyValue.get(keyValues[i]) !== true) {
								exitNodes[i] = group[i];
							}
						}
					} else {
						for(i = -1; ++i < n0;) {
							node = group[i];
							nodeData = groupData[i];
							if(node) {
								node.__data__ = nodeData;
								updateNodes[i] = node;
							} else {
								enterNodes[i] = d3_selection_dataNode(nodeData);
							}
						}
						for(; i < m; ++i) {
							enterNodes[i] = d3_selection_dataNode(groupData[i]);
						}
						for(; i < n; ++i) {
							exitNodes[i] = group[i];
						}
					}
					enterNodes.update = updateNodes;
					enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode;
					enter.push(enterNodes);
					update.push(updateNodes);
					exit.push(exitNodes);
				}
				var enter = d3_selection_enter([]),
					update = d3_selection([]),
					exit = d3_selection([]);
				if(typeof value === "function") {
					while(++i < n) {
						bind(group = this[i], value.call(group, group.parentNode.__data__, i));
					}
				} else {
					while(++i < n) {
						bind(group = this[i], value);
					}
				}
				update.enter = function() {
					return enter;
				};
				update.exit = function() {
					return exit;
				};
				return update;
			};

			function d3_selection_dataNode(data) {
				return {
					__data__: data
				};
			}
			d3_selectionPrototype.datum = function(value) {
				return arguments.length ? this.property("__data__", value) : this.property("__data__");
			};
			d3_selectionPrototype.filter = function(filter) {
				var subgroups = [],
					subgroup, group, node;
				if(typeof filter !== "function") filter = d3_selection_filter(filter);
				for(var j = 0, m = this.length; j < m; j++) {
					subgroups.push(subgroup = []);
					subgroup.parentNode = (group = this[j]).parentNode;
					for(var i = 0, n = group.length; i < n; i++) {
						if((node = group[i]) && filter.call(node, node.__data__, i, j)) {
							subgroup.push(node);
						}
					}
				}
				return d3_selection(subgroups);
			};

			function d3_selection_filter(selector) {
				return function() {
					return d3_selectMatches(this, selector);
				};
			}
			d3_selectionPrototype.order = function() {
				for(var j = -1, m = this.length; ++j < m;) {
					for(var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0;) {
						if(node = group[i]) {
							if(next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
							next = node;
						}
					}
				}
				return this;
			};
			d3_selectionPrototype.sort = function(comparator) {
				comparator = d3_selection_sortComparator.apply(this, arguments);
				for(var j = -1, m = this.length; ++j < m;) this[j].sort(comparator);
				return this.order();
			};

			function d3_selection_sortComparator(comparator) {
				if(!arguments.length) comparator = d3_ascending;
				return function(a, b) {
					return a && b ? comparator(a.__data__, b.__data__) : !a - !b;
				};
			}
			d3_selectionPrototype.each = function(callback) {
				return d3_selection_each(this, function(node, i, j) {
					callback.call(node, node.__data__, i, j);
				});
			};

			function d3_selection_each(groups, callback) {
				for(var j = 0, m = groups.length; j < m; j++) {
					for(var group = groups[j], i = 0, n = group.length, node; i < n; i++) {
						if(node = group[i]) callback(node, i, j);
					}
				}
				return groups;
			}
			d3_selectionPrototype.call = function(callback) {
				var args = d3_array(arguments);
				callback.apply(args[0] = this, args);
				return this;
			};
			d3_selectionPrototype.empty = function() {
				return !this.node();
			};
			d3_selectionPrototype.node = function() {
				for(var j = 0, m = this.length; j < m; j++) {
					for(var group = this[j], i = 0, n = group.length; i < n; i++) {
						var node = group[i];
						if(node) return node;
					}
				}
				return null;
			};
			d3_selectionPrototype.size = function() {
				var n = 0;
				d3_selection_each(this, function() {
					++n;
				});
				return n;
			};

			function d3_selection_enter(selection) {
				d3_subclass(selection, d3_selection_enterPrototype);
				return selection;
			}
			var d3_selection_enterPrototype = [];
			d3.selection.enter = d3_selection_enter;
			d3.selection.enter.prototype = d3_selection_enterPrototype;
			d3_selection_enterPrototype.append = d3_selectionPrototype.append;
			d3_selection_enterPrototype.empty = d3_selectionPrototype.empty;
			d3_selection_enterPrototype.node = d3_selectionPrototype.node;
			d3_selection_enterPrototype.call = d3_selectionPrototype.call;
			d3_selection_enterPrototype.size = d3_selectionPrototype.size;
			d3_selection_enterPrototype.select = function(selector) {
				var subgroups = [],
					subgroup, subnode, upgroup, group, node;
				for(var j = -1, m = this.length; ++j < m;) {
					upgroup = (group = this[j]).update;
					subgroups.push(subgroup = []);
					subgroup.parentNode = group.parentNode;
					for(var i = -1, n = group.length; ++i < n;) {
						if(node = group[i]) {
							subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j));
							subnode.__data__ = node.__data__;
						} else {
							subgroup.push(null);
						}
					}
				}
				return d3_selection(subgroups);
			};
			d3_selection_enterPrototype.insert = function(name, before) {
				if(arguments.length < 2) before = d3_selection_enterInsertBefore(this);
				return d3_selectionPrototype.insert.call(this, name, before);
			};

			function d3_selection_enterInsertBefore(enter) {
				var i0, j0;
				return function(d, i, j) {
					var group = enter[j].update,
						n = group.length,
						node;
					if(j != j0) j0 = j, i0 = 0;
					if(i >= i0) i0 = i + 1;
					while(!(node = group[i0]) && ++i0 < n);
					return node;
				};
			}
			d3.select = function(node) {
				var group;
				if(typeof node === "string") {
					group = [d3_select(node, d3_document)];
					group.parentNode = d3_document.documentElement;
				} else {
					group = [node];
					group.parentNode = d3_documentElement(node);
				}
				return d3_selection([group]);
			};
			d3.selectAll = function(nodes) {
				var group;
				if(typeof nodes === "string") {
					group = d3_array(d3_selectAll(nodes, d3_document));
					group.parentNode = d3_document.documentElement;
				} else {
					group = nodes;
					group.parentNode = null;
				}
				return d3_selection([group]);
			};
			d3_selectionPrototype.on = function(type, listener, capture) {
				var n = arguments.length;
				if(n < 3) {
					if(typeof type !== "string") {
						if(n < 2) listener = false;
						for(capture in type) this.each(d3_selection_on(capture, type[capture], listener));
						return this;
					}
					if(n < 2) return(n = this.node()["__on" + type]) && n._;
					capture = false;
				}
				return this.each(d3_selection_on(type, listener, capture));
			};

			function d3_selection_on(type, listener, capture) {
				var name = "__on" + type,
					i = type.indexOf("."),
					wrap = d3_selection_onListener;
				if(i > 0) type = type.slice(0, i);
				var filter = d3_selection_onFilters.get(type);
				if(filter) type = filter, wrap = d3_selection_onFilter;

				function onRemove() {
					var l = this[name];
					if(l) {
						this.removeEventListener(type, l, l.$);
						delete this[name];
					}
				}

				function onAdd() {
					var l = wrap(listener, d3_array(arguments));
					onRemove.call(this);
					this.addEventListener(type, this[name] = l, l.$ = capture);
					l._ = listener;
				}

				function removeAll() {
					var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"),
						match;
					for(var name in this) {
						if(match = name.match(re)) {
							var l = this[name];
							this.removeEventListener(match[1], l, l.$);
							delete this[name];
						}
					}
				}
				return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll;
			}
			var d3_selection_onFilters = d3.map({
				mouseenter: "mouseover",
				mouseleave: "mouseout"
			});
			if(d3_document) {
				d3_selection_onFilters.forEach(function(k) {
					if("on" + k in d3_document) d3_selection_onFilters.remove(k);
				});
			}

			function d3_selection_onListener(listener, argumentz) {
				return function(e) {
					var o = d3.event;
					d3.event = e;
					argumentz[0] = this.__data__;
					try {
						listener.apply(this, argumentz);
					} finally {
						d3.event = o;
					}
				};
			}

			function d3_selection_onFilter(listener, argumentz) {
				var l = d3_selection_onListener(listener, argumentz);
				return function(e) {
					var target = this,
						related = e.relatedTarget;
					if(!related || related !== target && !(related.compareDocumentPosition(target) & 8)) {
						l.call(target, e);
					}
				};
			}
			var d3_event_dragSelect, d3_event_dragId = 0;

			function d3_event_dragSuppress(node) {
				var name = ".dragsuppress-" + ++d3_event_dragId,
					click = "click" + name,
					w = d3.select(d3_window(node)).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault);
				if(d3_event_dragSelect == null) {
					d3_event_dragSelect = "onselectstart" in node ? false : d3_vendorSymbol(node.style, "userSelect");
				}
				if(d3_event_dragSelect) {
					var style = d3_documentElement(node).style,
						select = style[d3_event_dragSelect];
					style[d3_event_dragSelect] = "none";
				}
				return function(suppressClick) {
					w.on(name, null);
					if(d3_event_dragSelect) style[d3_event_dragSelect] = select;
					if(suppressClick) {
						var off = function() {
							w.on(click, null);
						};
						w.on(click, function() {
							d3_eventPreventDefault();
							off();
						}, true);
						setTimeout(off, 0);
					}
				};
			}
			d3.mouse = function(container) {
				return d3_mousePoint(container, d3_eventSource());
			};
			var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0;

			function d3_mousePoint(container, e) {
				if(e.changedTouches) e = e.changedTouches[0];
				var svg = container.ownerSVGElement || container;
				if(svg.createSVGPoint) {
					var point = svg.createSVGPoint();
					if(d3_mouse_bug44083 < 0) {
						var window = d3_window(container);
						if(window.scrollX || window.scrollY) {
							svg = d3.select("body").append("svg").style({
								position: "absolute",
								top: 0,
								left: 0,
								margin: 0,
								padding: 0,
								border: "none"
							}, "important");
							var ctm = svg[0][0].getScreenCTM();
							d3_mouse_bug44083 = !(ctm.f || ctm.e);
							svg.remove();
						}
					}
					if(d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY;
					else point.x = e.clientX,
						point.y = e.clientY;
					point = point.matrixTransform(container.getScreenCTM().inverse());
					return [point.x, point.y];
				}
				var rect = container.getBoundingClientRect();
				return [e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop];
			}
			d3.touch = function(container, touches, identifier) {
				if(arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches;
				if(touches)
					for(var i = 0, n = touches.length, touch; i < n; ++i) {
						if((touch = touches[i]).identifier === identifier) {
							return d3_mousePoint(container, touch);
						}
					}
			};
			d3.behavior.drag = function() {
				var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"),
					origin = null,
					mousedown = dragstart(d3_noop, d3.mouse, d3_window, "mousemove", "mouseup"),
					touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_identity, "touchmove", "touchend");

				function drag() {
					this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart);
				}

				function dragstart(id, position, subject, move, end) {
					return function() {
						var that = this,
							target = d3.event.target,
							parent = that.parentNode,
							dispatch = event.of(that, arguments),
							dragged = 0,
							dragId = id(),
							dragName = ".drag" + (dragId == null ? "" : "-" + dragId),
							dragOffset, dragSubject = d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, ended),
							dragRestore = d3_event_dragSuppress(target),
							position0 = position(parent, dragId);
						if(origin) {
							dragOffset = origin.apply(that, arguments);
							dragOffset = [dragOffset.x - position0[0], dragOffset.y - position0[1]];
						} else {
							dragOffset = [0, 0];
						}
						dispatch({
							type: "dragstart"
						});

						function moved() {
							var position1 = position(parent, dragId),
								dx, dy;
							if(!position1) return;
							dx = position1[0] - position0[0];
							dy = position1[1] - position0[1];
							dragged |= dx | dy;
							position0 = position1;
							dispatch({
								type: "drag",
								x: position1[0] + dragOffset[0],
								y: position1[1] + dragOffset[1],
								dx: dx,
								dy: dy
							});
						}

						function ended() {
							if(!position(parent, dragId)) return;
							dragSubject.on(move + dragName, null).on(end + dragName, null);
							dragRestore(dragged && d3.event.target === target);
							dispatch({
								type: "dragend"
							});
						}
					};
				}
				drag.origin = function(x) {
					if(!arguments.length) return origin;
					origin = x;
					return drag;
				};
				return d3.rebind(drag, event, "on");
			};

			function d3_behavior_dragTouchId() {
				return d3.event.changedTouches[0].identifier;
			}
			d3.touches = function(container, touches) {
				if(arguments.length < 2) touches = d3_eventSource().touches;
				return touches ? d3_array(touches).map(function(touch) {
					var point = d3_mousePoint(container, touch);
					point.identifier = touch.identifier;
					return point;
				}) : [];
			};
			var ε = 1e-6,
				ε2 = ε * ε,
				π = Math.PI,
				τ = 2 * π,
				τε = τ - ε,
				halfπ = π / 2,
				d3_radians = π / 180,
				d3_degrees = 180 / π;

			function d3_sgn(x) {
				return x > 0 ? 1 : x < 0 ? -1 : 0;
			}

			function d3_cross2d(a, b, c) {
				return(b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
			}

			function d3_acos(x) {
				return x > 1 ? 0 : x < -1 ? π : Math.acos(x);
			}

			function d3_asin(x) {
				return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x);
			}

			function d3_sinh(x) {
				return((x = Math.exp(x)) - 1 / x) / 2;
			}

			function d3_cosh(x) {
				return((x = Math.exp(x)) + 1 / x) / 2;
			}

			function d3_tanh(x) {
				return((x = Math.exp(2 * x)) - 1) / (x + 1);
			}

			function d3_haversin(x) {
				return(x = Math.sin(x / 2)) * x;
			}
			var ρ = Math.SQRT2,
				ρ2 = 2,
				ρ4 = 4;
			d3.interpolateZoom = function(p0, p1) {
				var ux0 = p0[0],
					uy0 = p0[1],
					w0 = p0[2],
					ux1 = p1[0],
					uy1 = p1[1],
					w1 = p1[2];
				var dx = ux1 - ux0,
					dy = uy1 - uy0,
					d2 = dx * dx + dy * dy,
					d1 = Math.sqrt(d2),
					b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1),
					b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1),
					r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0),
					r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1),
					dr = r1 - r0,
					S = (dr || Math.log(w1 / w0)) / ρ;

				function interpolate(t) {
					var s = t * S;
					if(dr) {
						var coshr0 = d3_cosh(r0),
							u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0));
						return [ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0)];
					}
					return [ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * s)];
				}
				interpolate.duration = S * 1e3;
				return interpolate;
			};
			d3.behavior.zoom = function() {
				var view = {
						x: 0,
						y: 0,
						k: 1
					},
					translate0, center0, center, size = [960, 500],
					scaleExtent = d3_behavior_zoomInfinity,
					duration = 250,
					zooming = 0,
					mousedown = "mousedown.zoom",
					mousemove = "mousemove.zoom",
					mouseup = "mouseup.zoom",
					mousewheelTimer, touchstart = "touchstart.zoom",
					touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"),
					x0, x1, y0, y1;
				if(!d3_behavior_zoomWheel) {
					d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() {
						return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1);
					}, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() {
						return d3.event.wheelDelta;
					}, "mousewheel") : (d3_behavior_zoomDelta = function() {
						return -d3.event.detail;
					}, "MozMousePixelScroll");
				}

				function zoom(g) {
					g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted);
				}
				zoom.event = function(g) {
					g.each(function() {
						var dispatch = event.of(this, arguments),
							view1 = view;
						if(d3_transitionInheritId) {
							d3.select(this).transition().each("start.zoom", function() {
								view = this.__chart__ || {
									x: 0,
									y: 0,
									k: 1
								};
								zoomstarted(dispatch);
							}).tween("zoom:zoom", function() {
								var dx = size[0],
									dy = size[1],
									cx = center0 ? center0[0] : dx / 2,
									cy = center0 ? center0[1] : dy / 2,
									i = d3.interpolateZoom([(cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k], [(cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k]);
								return function(t) {
									var l = i(t),
										k = dx / l[2];
									this.__chart__ = view = {
										x: cx - l[0] * k,
										y: cy - l[1] * k,
										k: k
									};
									zoomed(dispatch);
								};
							}).each("interrupt.zoom", function() {
								zoomended(dispatch);
							}).each("end.zoom", function() {
								zoomended(dispatch);
							});
						} else {
							this.__chart__ = view;
							zoomstarted(dispatch);
							zoomed(dispatch);
							zoomended(dispatch);
						}
					});
				};
				zoom.translate = function(_) {
					if(!arguments.length) return [view.x, view.y];
					view = {
						x: +_[0],
						y: +_[1],
						k: view.k
					};
					rescale();
					return zoom;
				};
				zoom.scale = function(_) {
					if(!arguments.length) return view.k;
					view = {
						x: view.x,
						y: view.y,
						k: +_
					};
					rescale();
					return zoom;
				};
				zoom.scaleExtent = function(_) {
					if(!arguments.length) return scaleExtent;
					scaleExtent = _ == null ? d3_behavior_zoomInfinity : [+_[0], +_[1]];
					return zoom;
				};
				zoom.center = function(_) {
					if(!arguments.length) return center;
					center = _ && [+_[0], +_[1]];
					return zoom;
				};
				zoom.size = function(_) {
					if(!arguments.length) return size;
					size = _ && [+_[0], +_[1]];
					return zoom;
				};
				zoom.duration = function(_) {
					if(!arguments.length) return duration;
					duration = +_;
					return zoom;
				};
				zoom.x = function(z) {
					if(!arguments.length) return x1;
					x1 = z;
					x0 = z.copy();
					view = {
						x: 0,
						y: 0,
						k: 1
					};
					return zoom;
				};
				zoom.y = function(z) {
					if(!arguments.length) return y1;
					y1 = z;
					y0 = z.copy();
					view = {
						x: 0,
						y: 0,
						k: 1
					};
					return zoom;
				};

				function location(p) {
					return [(p[0] - view.x) / view.k, (p[1] - view.y) / view.k];
				}

				function point(l) {
					return [l[0] * view.k + view.x, l[1] * view.k + view.y];
				}

				function scaleTo(s) {
					view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s));
				}

				function translateTo(p, l) {
					l = point(l);
					view.x += p[0] - l[0];
					view.y += p[1] - l[1];
				}

				function zoomTo(that, p, l, k) {
					that.__chart__ = {
						x: view.x,
						y: view.y,
						k: view.k
					};
					scaleTo(Math.pow(2, k));
					translateTo(center0 = p, l);
					that = d3.select(that);
					if(duration > 0) that = that.transition().duration(duration);
					that.call(zoom.event);
				}

				function rescale() {
					if(x1) x1.domain(x0.range().map(function(x) {
						return(x - view.x) / view.k;
					}).map(x0.invert));
					if(y1) y1.domain(y0.range().map(function(y) {
						return(y - view.y) / view.k;
					}).map(y0.invert));
				}

				function zoomstarted(dispatch) {
					if(!zooming++) dispatch({
						type: "zoomstart"
					});
				}

				function zoomed(dispatch) {
					rescale();
					dispatch({
						type: "zoom",
						scale: view.k,
						translate: [view.x, view.y]
					});
				}

				function zoomended(dispatch) {
					if(!--zooming) dispatch({
						type: "zoomend"
					}), center0 = null;
				}

				function mousedowned() {
					var that = this,
						target = d3.event.target,
						dispatch = event.of(that, arguments),
						dragged = 0,
						subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended),
						location0 = location(d3.mouse(that)),
						dragRestore = d3_event_dragSuppress(that);
					d3_selection_interrupt.call(that);
					zoomstarted(dispatch);

					function moved() {
						dragged = 1;
						translateTo(d3.mouse(that), location0);
						zoomed(dispatch);
					}

					function ended() {
						subject.on(mousemove, null).on(mouseup, null);
						dragRestore(dragged && d3.event.target === target);
						zoomended(dispatch);
					}
				}

				function touchstarted() {
					var that = this,
						dispatch = event.of(that, arguments),
						locations0 = {},
						distance0 = 0,
						scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier,
						touchmove = "touchmove" + zoomName,
						touchend = "touchend" + zoomName,
						targets = [],
						subject = d3.select(that),
						dragRestore = d3_event_dragSuppress(that);
					started();
					zoomstarted(dispatch);
					subject.on(mousedown, null).on(touchstart, started);

					function relocate() {
						var touches = d3.touches(that);
						scale0 = view.k;
						touches.forEach(function(t) {
							if(t.identifier in locations0) locations0[t.identifier] = location(t);
						});
						return touches;
					}

					function started() {
						var target = d3.event.target;
						d3.select(target).on(touchmove, moved).on(touchend, ended);
						targets.push(target);
						var changed = d3.event.changedTouches;
						for(var i = 0, n = changed.length; i < n; ++i) {
							locations0[changed[i].identifier] = null;
						}
						var touches = relocate(),
							now = Date.now();
						if(touches.length === 1) {
							if(now - touchtime < 500) {
								var p = touches[0];
								zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1);
								d3_eventPreventDefault();
							}
							touchtime = now;
						} else if(touches.length > 1) {
							var p = touches[0],
								q = touches[1],
								dx = p[0] - q[0],
								dy = p[1] - q[1];
							distance0 = dx * dx + dy * dy;
						}
					}

					function moved() {
						var touches = d3.touches(that),
							p0, l0, p1, l1;
						d3_selection_interrupt.call(that);
						for(var i = 0, n = touches.length; i < n; ++i, l1 = null) {
							p1 = touches[i];
							if(l1 = locations0[p1.identifier]) {
								if(l0) break;
								p0 = p1, l0 = l1;
							}
						}
						if(l1) {
							var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1,
								scale1 = distance0 && Math.sqrt(distance1 / distance0);
							p0 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2];
							l0 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2];
							scaleTo(scale1 * scale0);
						}
						touchtime = null;
						translateTo(p0, l0);
						zoomed(dispatch);
					}

					function ended() {
						if(d3.event.touches.length) {
							var changed = d3.event.changedTouches;
							for(var i = 0, n = changed.length; i < n; ++i) {
								delete locations0[changed[i].identifier];
							}
							for(var identifier in locations0) {
								return void relocate();
							}
						}
						d3.selectAll(targets).on(zoomName, null);
						subject.on(mousedown, mousedowned).on(touchstart, touchstarted);
						dragRestore();
						zoomended(dispatch);
					}
				}

				function mousewheeled() {
					var dispatch = event.of(this, arguments);
					if(mousewheelTimer) clearTimeout(mousewheelTimer);
					else d3_selection_interrupt.call(this),
						translate0 = location(center0 = center || d3.mouse(this)), zoomstarted(dispatch);
					mousewheelTimer = setTimeout(function() {
						mousewheelTimer = null;
						zoomended(dispatch);
					}, 50);
					d3_eventPreventDefault();
					scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k);
					translateTo(center0, translate0);
					zoomed(dispatch);
				}

				function dblclicked() {
					var p = d3.mouse(this),
						k = Math.log(view.k) / Math.LN2;
					zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1);
				}
				return d3.rebind(zoom, event, "on");
			};
			var d3_behavior_zoomInfinity = [0, Infinity],
				d3_behavior_zoomDelta, d3_behavior_zoomWheel;
			d3.color = d3_color;

			function d3_color() {}
			d3_color.prototype.toString = function() {
				return this.rgb() + "";
			};
			d3.hsl = d3_hsl;

			function d3_hsl(h, s, l) {
				return this instanceof d3_hsl ? void(this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l);
			}
			var d3_hslPrototype = d3_hsl.prototype = new d3_color();
			d3_hslPrototype.brighter = function(k) {
				k = Math.pow(.7, arguments.length ? k : 1);
				return new d3_hsl(this.h, this.s, this.l / k);
			};
			d3_hslPrototype.darker = function(k) {
				k = Math.pow(.7, arguments.length ? k : 1);
				return new d3_hsl(this.h, this.s, k * this.l);
			};
			d3_hslPrototype.rgb = function() {
				return d3_hsl_rgb(this.h, this.s, this.l);
			};

			function d3_hsl_rgb(h, s, l) {
				var m1, m2;
				h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h;
				s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s;
				l = l < 0 ? 0 : l > 1 ? 1 : l;
				m2 = l <= .5 ? l * (1 + s) : l + s - l * s;
				m1 = 2 * l - m2;

				function v(h) {
					if(h > 360) h -= 360;
					else if(h < 0) h += 360;
					if(h < 60) return m1 + (m2 - m1) * h / 60;
					if(h < 180) return m2;
					if(h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
					return m1;
				}

				function vv(h) {
					return Math.round(v(h) * 255);
				}
				return new d3_rgb(vv(h + 120), vv(h), vv(h - 120));
			}
			d3.hcl = d3_hcl;

			function d3_hcl(h, c, l) {
				return this instanceof d3_hcl ? void(this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l);
			}
			var d3_hclPrototype = d3_hcl.prototype = new d3_color();
			d3_hclPrototype.brighter = function(k) {
				return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)));
			};
			d3_hclPrototype.darker = function(k) {
				return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)));
			};
			d3_hclPrototype.rgb = function() {
				return d3_hcl_lab(this.h, this.c, this.l).rgb();
			};

			function d3_hcl_lab(h, c, l) {
				if(isNaN(h)) h = 0;
				if(isNaN(c)) c = 0;
				return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c);
			}
			d3.lab = d3_lab;

			function d3_lab(l, a, b) {
				return this instanceof d3_lab ? void(this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b);
			}
			var d3_lab_K = 18;
			var d3_lab_X = .95047,
				d3_lab_Y = 1,
				d3_lab_Z = 1.08883;
			var d3_labPrototype = d3_lab.prototype = new d3_color();
			d3_labPrototype.brighter = function(k) {
				return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
			};
			d3_labPrototype.darker = function(k) {
				return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
			};
			d3_labPrototype.rgb = function() {
				return d3_lab_rgb(this.l, this.a, this.b);
			};

			function d3_lab_rgb(l, a, b) {
				var y = (l + 16) / 116,
					x = y + a / 500,
					z = y - b / 200;
				x = d3_lab_xyz(x) * d3_lab_X;
				y = d3_lab_xyz(y) * d3_lab_Y;
				z = d3_lab_xyz(z) * d3_lab_Z;
				return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z));
			}

			function d3_lab_hcl(l, a, b) {
				return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l);
			}

			function d3_lab_xyz(x) {
				return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
			}

			function d3_xyz_lab(x) {
				return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
			}

			function d3_xyz_rgb(r) {
				return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055));
			}
			d3.rgb = d3_rgb;

			function d3_rgb(r, g, b) {
				return this instanceof d3_rgb ? void(this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b);
			}

			function d3_rgbNumber(value) {
				return new d3_rgb(value >> 16, value >> 8 & 255, value & 255);
			}

			function d3_rgbString(value) {
				return d3_rgbNumber(value) + "";
			}
			var d3_rgbPrototype = d3_rgb.prototype = new d3_color();
			d3_rgbPrototype.brighter = function(k) {
				k = Math.pow(.7, arguments.length ? k : 1);
				var r = this.r,
					g = this.g,
					b = this.b,
					i = 30;
				if(!r && !g && !b) return new d3_rgb(i, i, i);
				if(r && r < i) r = i;
				if(g && g < i) g = i;
				if(b && b < i) b = i;
				return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k));
			};
			d3_rgbPrototype.darker = function(k) {
				k = Math.pow(.7, arguments.length ? k : 1);
				return new d3_rgb(k * this.r, k * this.g, k * this.b);
			};
			d3_rgbPrototype.hsl = function() {
				return d3_rgb_hsl(this.r, this.g, this.b);
			};
			d3_rgbPrototype.toString = function() {
				return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b);
			};

			function d3_rgb_hex(v) {
				return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16);
			}

			function d3_rgb_parse(format, rgb, hsl) {
				format = format.toLowerCase();
				var r = 0,
					g = 0,
					b = 0,
					m1, m2, color;
				m1 = /([a-z]+)\((.*)\)/.exec(format);
				if(m1) {
					m2 = m1[2].split(",");
					switch(m1[1]) {
						case "hsl":
							{
								return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100);
							}

						case "rgb":
							{
								return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2]));
							}
					}
				}
				if(color = d3_rgb_names.get(format)) {
					return rgb(color.r, color.g, color.b);
				}
				if(format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) {
					if(format.length === 4) {
						r = (color & 3840) >> 4;
						r = r >> 4 | r;
						g = color & 240;
						g = g >> 4 | g;
						b = color & 15;
						b = b << 4 | b;
					} else if(format.length === 7) {
						r = (color & 16711680) >> 16;
						g = (color & 65280) >> 8;
						b = color & 255;
					}
				}
				return rgb(r, g, b);
			}

			function d3_rgb_hsl(r, g, b) {
				var min = Math.min(r /= 255, g /= 255, b /= 255),
					max = Math.max(r, g, b),
					d = max - min,
					h, s, l = (max + min) / 2;
				if(d) {
					s = l < .5 ? d / (max + min) : d / (2 - max - min);
					if(r == max) h = (g - b) / d + (g < b ? 6 : 0);
					else if(g == max) h = (b - r) / d + 2;
					else h = (r - g) / d + 4;
					h *= 60;
				} else {
					h = NaN;
					s = l > 0 && l < 1 ? 0 : h;
				}
				return new d3_hsl(h, s, l);
			}

			function d3_rgb_lab(r, g, b) {
				r = d3_rgb_xyz(r);
				g = d3_rgb_xyz(g);
				b = d3_rgb_xyz(b);
				var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X),
					y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y),
					z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z);
				return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z));
			}

			function d3_rgb_xyz(r) {
				return(r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4);
			}

			function d3_rgb_parseNumber(c) {
				var f = parseFloat(c);
				return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f;
			}
			var d3_rgb_names = d3.map({
				aliceblue: 15792383,
				antiquewhite: 16444375,
				aqua: 65535,
				aquamarine: 8388564,
				azure: 15794175,
				beige: 16119260,
				bisque: 16770244,
				black: 0,
				blanchedalmond: 16772045,
				blue: 255,
				blueviolet: 9055202,
				brown: 10824234,
				burlywood: 14596231,
				cadetblue: 6266528,
				chartreuse: 8388352,
				chocolate: 13789470,
				coral: 16744272,
				cornflowerblue: 6591981,
				cornsilk: 16775388,
				crimson: 14423100,
				cyan: 65535,
				darkblue: 139,
				darkcyan: 35723,
				darkgoldenrod: 12092939,
				darkgray: 11119017,
				darkgreen: 25600,
				darkgrey: 11119017,
				darkkhaki: 12433259,
				darkmagenta: 9109643,
				darkolivegreen: 5597999,
				darkorange: 16747520,
				darkorchid: 10040012,
				darkred: 9109504,
				darksalmon: 15308410,
				darkseagreen: 9419919,
				darkslateblue: 4734347,
				darkslategray: 3100495,
				darkslategrey: 3100495,
				darkturquoise: 52945,
				darkviolet: 9699539,
				deeppink: 16716947,
				deepskyblue: 49151,
				dimgray: 6908265,
				dimgrey: 6908265,
				dodgerblue: 2003199,
				firebrick: 11674146,
				floralwhite: 16775920,
				forestgreen: 2263842,
				fuchsia: 16711935,
				gainsboro: 14474460,
				ghostwhite: 16316671,
				gold: 16766720,
				goldenrod: 14329120,
				gray: 8421504,
				green: 32768,
				greenyellow: 11403055,
				grey: 8421504,
				honeydew: 15794160,
				hotpink: 16738740,
				indianred: 13458524,
				indigo: 4915330,
				ivory: 16777200,
				khaki: 15787660,
				lavender: 15132410,
				lavenderblush: 16773365,
				lawngreen: 8190976,
				lemonchiffon: 16775885,
				lightblue: 11393254,
				lightcoral: 15761536,
				lightcyan: 14745599,
				lightgoldenrodyellow: 16448210,
				lightgray: 13882323,
				lightgreen: 9498256,
				lightgrey: 13882323,
				lightpink: 16758465,
				lightsalmon: 16752762,
				lightseagreen: 2142890,
				lightskyblue: 8900346,
				lightslategray: 7833753,
				lightslategrey: 7833753,
				lightsteelblue: 11584734,
				lightyellow: 16777184,
				lime: 65280,
				limegreen: 3329330,
				linen: 16445670,
				magenta: 16711935,
				maroon: 8388608,
				mediumaquamarine: 6737322,
				mediumblue: 205,
				mediumorchid: 12211667,
				mediumpurple: 9662683,
				mediumseagreen: 3978097,
				mediumslateblue: 8087790,
				mediumspringgreen: 64154,
				mediumturquoise: 4772300,
				mediumvioletred: 13047173,
				midnightblue: 1644912,
				mintcream: 16121850,
				mistyrose: 16770273,
				moccasin: 16770229,
				navajowhite: 16768685,
				navy: 128,
				oldlace: 16643558,
				olive: 8421376,
				olivedrab: 7048739,
				orange: 16753920,
				orangered: 16729344,
				orchid: 14315734,
				palegoldenrod: 15657130,
				palegreen: 10025880,
				paleturquoise: 11529966,
				palevioletred: 14381203,
				papayawhip: 16773077,
				peachpuff: 16767673,
				peru: 13468991,
				pink: 16761035,
				plum: 14524637,
				powderblue: 11591910,
				purple: 8388736,
				rebeccapurple: 6697881,
				red: 16711680,
				rosybrown: 12357519,
				royalblue: 4286945,
				saddlebrown: 9127187,
				salmon: 16416882,
				sandybrown: 16032864,
				seagreen: 3050327,
				seashell: 16774638,
				sienna: 10506797,
				silver: 12632256,
				skyblue: 8900331,
				slateblue: 6970061,
				slategray: 7372944,
				slategrey: 7372944,
				snow: 16775930,
				springgreen: 65407,
				steelblue: 4620980,
				tan: 13808780,
				teal: 32896,
				thistle: 14204888,
				tomato: 16737095,
				turquoise: 4251856,
				violet: 15631086,
				wheat: 16113331,
				white: 16777215,
				whitesmoke: 16119285,
				yellow: 16776960,
				yellowgreen: 10145074
			});
			d3_rgb_names.forEach(function(key, value) {
				d3_rgb_names.set(key, d3_rgbNumber(value));
			});

			function d3_functor(v) {
				return typeof v === "function" ? v : function() {
					return v;
				};
			}
			d3.functor = d3_functor;
			d3.xhr = d3_xhrType(d3_identity);

			function d3_xhrType(response) {
				return function(url, mimeType, callback) {
					if(arguments.length === 2 && typeof mimeType === "function") callback = mimeType,
						mimeType = null;
					return d3_xhr(url, mimeType, response, callback);
				};
			}

			function d3_xhr(url, mimeType, response, callback) {
				var xhr = {},
					dispatch = d3.dispatch("beforesend", "progress", "load", "error"),
					headers = {},
					request = new XMLHttpRequest(),
					responseType = null;
				if(this.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest();
				"onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() {
					request.readyState > 3 && respond();
				};

				function respond() {
					var status = request.status,
						result;
					if(!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) {
						try {
							result = response.call(xhr, request);
						} catch(e) {
							dispatch.error.call(xhr, e);
							return;
						}
						dispatch.load.call(xhr, result);
					} else {
						dispatch.error.call(xhr, request);
					}
				}
				request.onprogress = function(event) {
					var o = d3.event;
					d3.event = event;
					try {
						dispatch.progress.call(xhr, request);
					} finally {
						d3.event = o;
					}
				};
				xhr.header = function(name, value) {
					name = (name + "").toLowerCase();
					if(arguments.length < 2) return headers[name];
					if(value == null) delete headers[name];
					else headers[name] = value + "";
					return xhr;
				};
				xhr.mimeType = function(value) {
					if(!arguments.length) return mimeType;
					mimeType = value == null ? null : value + "";
					return xhr;
				};
				xhr.responseType = function(value) {
					if(!arguments.length) return responseType;
					responseType = value;
					return xhr;
				};
				xhr.response = function(value) {
					response = value;
					return xhr;
				};
				["get", "post"].forEach(function(method) {
					xhr[method] = function() {
						return xhr.send.apply(xhr, [method].concat(d3_array(arguments)));
					};
				});
				xhr.send = function(method, data, callback) {
					if(arguments.length === 2 && typeof data === "function") callback = data, data = null;
					request.open(method, url, true);
					if(mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*";
					if(request.setRequestHeader)
						for(var name in headers) request.setRequestHeader(name, headers[name]);
					if(mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType);
					if(responseType != null) request.responseType = responseType;
					if(callback != null) xhr.on("error", callback).on("load", function(request) {
						callback(null, request);
					});
					dispatch.beforesend.call(xhr, request);
					request.send(data == null ? null : data);
					return xhr;
				};
				xhr.abort = function() {
					request.abort();
					return xhr;
				};
				d3.rebind(xhr, dispatch, "on");
				return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback));
			}

			function d3_xhr_fixCallback(callback) {
				return callback.length === 1 ? function(error, request) {
					callback(error == null ? request : null);
				} : callback;
			}

			function d3_xhrHasResponse(request) {
				var type = request.responseType;
				return type && type !== "text" ? request.response : request.responseText;
			}
			d3.dsv = function(delimiter, mimeType) {
				var reFormat = new RegExp('["' + delimiter + "\n]"),
					delimiterCode = delimiter.charCodeAt(0);

				function dsv(url, row, callback) {
					if(arguments.length < 3) callback = row, row = null;
					var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback);
					xhr.row = function(_) {
						return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row;
					};
					return xhr;
				}

				function response(request) {
					return dsv.parse(request.responseText);
				}

				function typedResponse(f) {
					return function(request) {
						return dsv.parse(request.responseText, f);
					};
				}
				dsv.parse = function(text, f) {
					var o;
					return dsv.parseRows(text, function(row, i) {
						if(o) return o(row, i - 1);
						var a = new Function("d", "return {" + row.map(function(name, i) {
							return JSON.stringify(name) + ": d[" + i + "]";
						}).join(",") + "}");
						o = f ? function(row, i) {
							return f(a(row), i);
						} : a;
					});
				};
				dsv.parseRows = function(text, f) {
					var EOL = {},
						EOF = {},
						rows = [],
						N = text.length,
						I = 0,
						n = 0,
						t, eol;

					function token() {
						if(I >= N) return EOF;
						if(eol) return eol = false, EOL;
						var j = I;
						if(text.charCodeAt(j) === 34) {
							var i = j;
							while(i++ < N) {
								if(text.charCodeAt(i) === 34) {
									if(text.charCodeAt(i + 1) !== 34) break;
									++i;
								}
							}
							I = i + 2;
							var c = text.charCodeAt(i + 1);
							if(c === 13) {
								eol = true;
								if(text.charCodeAt(i + 2) === 10) ++I;
							} else if(c === 10) {
								eol = true;
							}
							return text.slice(j + 1, i).replace(/""/g, '"');
						}
						while(I < N) {
							var c = text.charCodeAt(I++),
								k = 1;
							if(c === 10) eol = true;
							else if(c === 13) {
								eol = true;
								if(text.charCodeAt(I) === 10) ++I, ++k;
							} else if(c !== delimiterCode) continue;
							return text.slice(j, I - k);
						}
						return text.slice(j);
					}
					while((t = token()) !== EOF) {
						var a = [];
						while(t !== EOL && t !== EOF) {
							a.push(t);
							t = token();
						}
						if(f && (a = f(a, n++)) == null) continue;
						rows.push(a);
					}
					return rows;
				};
				dsv.format = function(rows) {
					if(Array.isArray(rows[0])) return dsv.formatRows(rows);
					var fieldSet = new d3_Set(),
						fields = [];
					rows.forEach(function(row) {
						for(var field in row) {
							if(!fieldSet.has(field)) {
								fields.push(fieldSet.add(field));
							}
						}
					});
					return [fields.map(formatValue).join(delimiter)].concat(rows.map(function(row) {
						return fields.map(function(field) {
							return formatValue(row[field]);
						}).join(delimiter);
					})).join("\n");
				};
				dsv.formatRows = function(rows) {
					return rows.map(formatRow).join("\n");
				};

				function formatRow(row) {
					return row.map(formatValue).join(delimiter);
				}

				function formatValue(text) {
					return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text;
				}
				return dsv;
			};
			d3.csv = d3.dsv(",", "text/csv");
			d3.tsv = d3.dsv("	", "text/tab-separated-values");
			var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_active, d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) {
				setTimeout(callback, 17);
			};
			d3.timer = function(callback, delay, then) {
				var n = arguments.length;
				if(n < 2) delay = 0;
				if(n < 3) then = Date.now();
				var time = then + delay,
					timer = {
						c: callback,
						t: time,
						f: false,
						n: null
					};
				if(d3_timer_queueTail) d3_timer_queueTail.n = timer;
				else d3_timer_queueHead = timer;
				d3_timer_queueTail = timer;
				if(!d3_timer_interval) {
					d3_timer_timeout = clearTimeout(d3_timer_timeout);
					d3_timer_interval = 1;
					d3_timer_frame(d3_timer_step);
				}
			};

			function d3_timer_step() {
				var now = d3_timer_mark(),
					delay = d3_timer_sweep() - now;
				if(delay > 24) {
					if(isFinite(delay)) {
						clearTimeout(d3_timer_timeout);
						d3_timer_timeout = setTimeout(d3_timer_step, delay);
					}
					d3_timer_interval = 0;
				} else {
					d3_timer_interval = 1;
					d3_timer_frame(d3_timer_step);
				}
			}
			d3.timer.flush = function() {
				d3_timer_mark();
				d3_timer_sweep();
			};

			function d3_timer_mark() {
				var now = Date.now();
				d3_timer_active = d3_timer_queueHead;
				while(d3_timer_active) {
					if(now >= d3_timer_active.t) d3_timer_active.f = d3_timer_active.c(now - d3_timer_active.t);
					d3_timer_active = d3_timer_active.n;
				}
				return now;
			}

			function d3_timer_sweep() {
				var t0, t1 = d3_timer_queueHead,
					time = Infinity;
				while(t1) {
					if(t1.f) {
						t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n;
					} else {
						if(t1.t < time) time = t1.t;
						t1 = (t0 = t1).n;
					}
				}
				d3_timer_queueTail = t0;
				return time;
			}

			function d3_format_precision(x, p) {
				return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1);
			}
			d3.round = function(x, n) {
				return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x);
			};
			var d3_formatPrefixes = ["y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y"].map(d3_formatPrefix);
			d3.formatPrefix = function(value, precision) {
				var i = 0;
				if(value) {
					if(value < 0) value *= -1;
					if(precision) value = d3.round(value, d3_format_precision(value, precision));
					i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
					i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3));
				}
				return d3_formatPrefixes[8 + i / 3];
			};

			function d3_formatPrefix(d, i) {
				var k = Math.pow(10, abs(8 - i) * 3);
				return {
					scale: i > 8 ? function(d) {
						return d / k;
					} : function(d) {
						return d * k;
					},
					symbol: d
				};
			}

			function d3_locale_numberFormat(locale) {
				var locale_decimal = locale.decimal,
					locale_thousands = locale.thousands,
					locale_grouping = locale.grouping,
					locale_currency = locale.currency,
					formatGroup = locale_grouping && locale_thousands ? function(value, width) {
						var i = value.length,
							t = [],
							j = 0,
							g = locale_grouping[0],
							length = 0;
						while(i > 0 && g > 0) {
							if(length + g + 1 > width) g = Math.max(1, width - length);
							t.push(value.substring(i -= g, i + g));
							if((length += g + 1) > width) break;
							g = locale_grouping[j = (j + 1) % locale_grouping.length];
						}
						return t.reverse().join(locale_thousands);
					} : d3_identity;
				return function(specifier) {
					var match = d3_format_re.exec(specifier),
						fill = match[1] || " ",
						align = match[2] || ">",
						sign = match[3] || "-",
						symbol = match[4] || "",
						zfill = match[5],
						width = +match[6],
						comma = match[7],
						precision = match[8],
						type = match[9],
						scale = 1,
						prefix = "",
						suffix = "",
						integer = false,
						exponent = true;
					if(precision) precision = +precision.substring(1);
					if(zfill || fill === "0" && align === "=") {
						zfill = fill = "0";
						align = "=";
					}
					switch(type) {
						case "n":
							comma = true;
							type = "g";
							break;

						case "%":
							scale = 100;
							suffix = "%";
							type = "f";
							break;

						case "p":
							scale = 100;
							suffix = "%";
							type = "r";
							break;

						case "b":
						case "o":
						case "x":
						case "X":
							if(symbol === "#") prefix = "0" + type.toLowerCase();

						case "c":
							exponent = false;

						case "d":
							integer = true;
							precision = 0;
							break;

						case "s":
							scale = -1;
							type = "r";
							break;
					}
					if(symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1];
					if(type == "r" && !precision) type = "g";
					if(precision != null) {
						if(type == "g") precision = Math.max(1, Math.min(21, precision));
						else if(type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision));
					}
					type = d3_format_types.get(type) || d3_format_typeDefault;
					var zcomma = zfill && comma;
					return function(value) {
						var fullSuffix = suffix;
						if(integer && value % 1) return "";
						var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign;
						if(scale < 0) {
							var unit = d3.formatPrefix(value, precision);
							value = unit.scale(value);
							fullSuffix = unit.symbol + suffix;
						} else {
							value *= scale;
						}
						value = type(value, precision);
						var i = value.lastIndexOf("."),
							before, after;
						if(i < 0) {
							var j = exponent ? value.lastIndexOf("e") : -1;
							if(j < 0) before = value, after = "";
							else before = value.substring(0, j), after = value.substring(j);
						} else {
							before = value.substring(0, i);
							after = locale_decimal + value.substring(i + 1);
						}
						if(!zfill && comma) before = formatGroup(before, Infinity);
						var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length),
							padding = length < width ? new Array(length = width - length + 1).join(fill) : "";
						if(zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity);
						negative += prefix;
						value = before + after;
						return(align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix;
					};
				};
			}
			var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
			var d3_format_types = d3.map({
				b: function(x) {
					return x.toString(2);
				},
				c: function(x) {
					return String.fromCharCode(x);
				},
				o: function(x) {
					return x.toString(8);
				},
				x: function(x) {
					return x.toString(16);
				},
				X: function(x) {
					return x.toString(16).toUpperCase();
				},
				g: function(x, p) {
					return x.toPrecision(p);
				},
				e: function(x, p) {
					return x.toExponential(p);
				},
				f: function(x, p) {
					return x.toFixed(p);
				},
				r: function(x, p) {
					return(x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p))));
				}
			});

			function d3_format_typeDefault(x) {
				return x + "";
			}
			var d3_time = d3.time = {},
				d3_date = Date;

			function d3_date_utc() {
				this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]);
			}
			d3_date_utc.prototype = {
				getDate: function() {
					return this._.getUTCDate();
				},
				getDay: function() {
					return this._.getUTCDay();
				},
				getFullYear: function() {
					return this._.getUTCFullYear();
				},
				getHours: function() {
					return this._.getUTCHours();
				},
				getMilliseconds: function() {
					return this._.getUTCMilliseconds();
				},
				getMinutes: function() {
					return this._.getUTCMinutes();
				},
				getMonth: function() {
					return this._.getUTCMonth();
				},
				getSeconds: function() {
					return this._.getUTCSeconds();
				},
				getTime: function() {
					return this._.getTime();
				},
				getTimezoneOffset: function() {
					return 0;
				},
				valueOf: function() {
					return this._.valueOf();
				},
				setDate: function() {
					d3_time_prototype.setUTCDate.apply(this._, arguments);
				},
				setDay: function() {
					d3_time_prototype.setUTCDay.apply(this._, arguments);
				},
				setFullYear: function() {
					d3_time_prototype.setUTCFullYear.apply(this._, arguments);
				},
				setHours: function() {
					d3_time_prototype.setUTCHours.apply(this._, arguments);
				},
				setMilliseconds: function() {
					d3_time_prototype.setUTCMilliseconds.apply(this._, arguments);
				},
				setMinutes: function() {
					d3_time_prototype.setUTCMinutes.apply(this._, arguments);
				},
				setMonth: function() {
					d3_time_prototype.setUTCMonth.apply(this._, arguments);
				},
				setSeconds: function() {
					d3_time_prototype.setUTCSeconds.apply(this._, arguments);
				},
				setTime: function() {
					d3_time_prototype.setTime.apply(this._, arguments);
				}
			};
			var d3_time_prototype = Date.prototype;

			function d3_time_interval(local, step, number) {
				function round(date) {
					var d0 = local(date),
						d1 = offset(d0, 1);
					return date - d0 < d1 - date ? d0 : d1;
				}

				function ceil(date) {
					step(date = local(new d3_date(date - 1)), 1);
					return date;
				}

				function offset(date, k) {
					step(date = new d3_date(+date), k);
					return date;
				}

				function range(t0, t1, dt) {
					var time = ceil(t0),
						times = [];
					if(dt > 1) {
						while(time < t1) {
							if(!(number(time) % dt)) times.push(new Date(+time));
							step(time, 1);
						}
					} else {
						while(time < t1) times.push(new Date(+time)), step(time, 1);
					}
					return times;
				}

				function range_utc(t0, t1, dt) {
					try {
						d3_date = d3_date_utc;
						var utc = new d3_date_utc();
						utc._ = t0;
						return range(utc, t1, dt);
					} finally {
						d3_date = Date;
					}
				}
				local.floor = local;
				local.round = round;
				local.ceil = ceil;
				local.offset = offset;
				local.range = range;
				var utc = local.utc = d3_time_interval_utc(local);
				utc.floor = utc;
				utc.round = d3_time_interval_utc(round);
				utc.ceil = d3_time_interval_utc(ceil);
				utc.offset = d3_time_interval_utc(offset);
				utc.range = range_utc;
				return local;
			}

			function d3_time_interval_utc(method) {
				return function(date, k) {
					try {
						d3_date = d3_date_utc;
						var utc = new d3_date_utc();
						utc._ = date;
						return method(utc, k)._;
					} finally {
						d3_date = Date;
					}
				};
			}
			d3_time.year = d3_time_interval(function(date) {
				date = d3_time.day(date);
				date.setMonth(0, 1);
				return date;
			}, function(date, offset) {
				date.setFullYear(date.getFullYear() + offset);
			}, function(date) {
				return date.getFullYear();
			});
			d3_time.years = d3_time.year.range;
			d3_time.years.utc = d3_time.year.utc.range;
			d3_time.day = d3_time_interval(function(date) {
				var day = new d3_date(2e3, 0);
				day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
				return day;
			}, function(date, offset) {
				date.setDate(date.getDate() + offset);
			}, function(date) {
				return date.getDate() - 1;
			});
			d3_time.days = d3_time.day.range;
			d3_time.days.utc = d3_time.day.utc.range;
			d3_time.dayOfYear = function(date) {
				var year = d3_time.year(date);
				return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5);
			};
			["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"].forEach(function(day, i) {
				i = 7 - i;
				var interval = d3_time[day] = d3_time_interval(function(date) {
					(date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7);
					return date;
				}, function(date, offset) {
					date.setDate(date.getDate() + Math.floor(offset) * 7);
				}, function(date) {
					var day = d3_time.year(date).getDay();
					return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i);
				});
				d3_time[day + "s"] = interval.range;
				d3_time[day + "s"].utc = interval.utc.range;
				d3_time[day + "OfYear"] = function(date) {
					var day = d3_time.year(date).getDay();
					return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7);
				};
			});
			d3_time.week = d3_time.sunday;
			d3_time.weeks = d3_time.sunday.range;
			d3_time.weeks.utc = d3_time.sunday.utc.range;
			d3_time.weekOfYear = d3_time.sundayOfYear;

			function d3_locale_timeFormat(locale) {
				var locale_dateTime = locale.dateTime,
					locale_date = locale.date,
					locale_time = locale.time,
					locale_periods = locale.periods,
					locale_days = locale.days,
					locale_shortDays = locale.shortDays,
					locale_months = locale.months,
					locale_shortMonths = locale.shortMonths;

				function d3_time_format(template) {
					var n = template.length;

					function format(date) {
						var string = [],
							i = -1,
							j = 0,
							c, p, f;
						while(++i < n) {
							if(template.charCodeAt(i) === 37) {
								string.push(template.slice(j, i));
								if((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i);
								if(f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p);
								string.push(c);
								j = i + 1;
							}
						}
						string.push(template.slice(j, i));
						return string.join("");
					}
					format.parse = function(string) {
						var d = {
								y: 1900,
								m: 0,
								d: 1,
								H: 0,
								M: 0,
								S: 0,
								L: 0,
								Z: null
							},
							i = d3_time_parse(d, template, string, 0);
						if(i != string.length) return null;
						if("p" in d) d.H = d.H % 12 + d.p * 12;
						var localZ = d.Z != null && d3_date !== d3_date_utc,
							date = new(localZ ? d3_date_utc : d3_date)();
						if("j" in d) date.setFullYear(d.y, 0, d.j);
						else if("w" in d && ("W" in d || "U" in d)) {
							date.setFullYear(d.y, 0, 1);
							date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7);
						} else date.setFullYear(d.y, d.m, d.d);
						date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L);
						return localZ ? date._ : date;
					};
					format.toString = function() {
						return template;
					};
					return format;
				}

				function d3_time_parse(date, template, string, j) {
					var c, p, t, i = 0,
						n = template.length,
						m = string.length;
					while(i < n) {
						if(j >= m) return -1;
						c = template.charCodeAt(i++);
						if(c === 37) {
							t = template.charAt(i++);
							p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t];
							if(!p || (j = p(date, string, j)) < 0) return -1;
						} else if(c != string.charCodeAt(j++)) {
							return -1;
						}
					}
					return j;
				}
				d3_time_format.utc = function(template) {
					var local = d3_time_format(template);

					function format(date) {
						try {
							d3_date = d3_date_utc;
							var utc = new d3_date();
							utc._ = date;
							return local(utc);
						} finally {
							d3_date = Date;
						}
					}
					format.parse = function(string) {
						try {
							d3_date = d3_date_utc;
							var date = local.parse(string);
							return date && date._;
						} finally {
							d3_date = Date;
						}
					};
					format.toString = local.toString;
					return format;
				};
				d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti;
				var d3_time_periodLookup = d3.map(),
					d3_time_dayRe = d3_time_formatRe(locale_days),
					d3_time_dayLookup = d3_time_formatLookup(locale_days),
					d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays),
					d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays),
					d3_time_monthRe = d3_time_formatRe(locale_months),
					d3_time_monthLookup = d3_time_formatLookup(locale_months),
					d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths),
					d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths);
				locale_periods.forEach(function(p, i) {
					d3_time_periodLookup.set(p.toLowerCase(), i);
				});
				var d3_time_formats = {
					a: function(d) {
						return locale_shortDays[d.getDay()];
					},
					A: function(d) {
						return locale_days[d.getDay()];
					},
					b: function(d) {
						return locale_shortMonths[d.getMonth()];
					},
					B: function(d) {
						return locale_months[d.getMonth()];
					},
					c: d3_time_format(locale_dateTime),
					d: function(d, p) {
						return d3_time_formatPad(d.getDate(), p, 2);
					},
					e: function(d, p) {
						return d3_time_formatPad(d.getDate(), p, 2);
					},
					H: function(d, p) {
						return d3_time_formatPad(d.getHours(), p, 2);
					},
					I: function(d, p) {
						return d3_time_formatPad(d.getHours() % 12 || 12, p, 2);
					},
					j: function(d, p) {
						return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3);
					},
					L: function(d, p) {
						return d3_time_formatPad(d.getMilliseconds(), p, 3);
					},
					m: function(d, p) {
						return d3_time_formatPad(d.getMonth() + 1, p, 2);
					},
					M: function(d, p) {
						return d3_time_formatPad(d.getMinutes(), p, 2);
					},
					p: function(d) {
						return locale_periods[+(d.getHours() >= 12)];
					},
					S: function(d, p) {
						return d3_time_formatPad(d.getSeconds(), p, 2);
					},
					U: function(d, p) {
						return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2);
					},
					w: function(d) {
						return d.getDay();
					},
					W: function(d, p) {
						return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2);
					},
					x: d3_time_format(locale_date),
					X: d3_time_format(locale_time),
					y: function(d, p) {
						return d3_time_formatPad(d.getFullYear() % 100, p, 2);
					},
					Y: function(d, p) {
						return d3_time_formatPad(d.getFullYear() % 1e4, p, 4);
					},
					Z: d3_time_zone,
					"%": function() {
						return "%";
					}
				};
				var d3_time_parsers = {
					a: d3_time_parseWeekdayAbbrev,
					A: d3_time_parseWeekday,
					b: d3_time_parseMonthAbbrev,
					B: d3_time_parseMonth,
					c: d3_time_parseLocaleFull,
					d: d3_time_parseDay,
					e: d3_time_parseDay,
					H: d3_time_parseHour24,
					I: d3_time_parseHour24,
					j: d3_time_parseDayOfYear,
					L: d3_time_parseMilliseconds,
					m: d3_time_parseMonthNumber,
					M: d3_time_parseMinutes,
					p: d3_time_parseAmPm,
					S: d3_time_parseSeconds,
					U: d3_time_parseWeekNumberSunday,
					w: d3_time_parseWeekdayNumber,
					W: d3_time_parseWeekNumberMonday,
					x: d3_time_parseLocaleDate,
					X: d3_time_parseLocaleTime,
					y: d3_time_parseYear,
					Y: d3_time_parseFullYear,
					Z: d3_time_parseZone,
					"%": d3_time_parseLiteralPercent
				};

				function d3_time_parseWeekdayAbbrev(date, string, i) {
					d3_time_dayAbbrevRe.lastIndex = 0;
					var n = d3_time_dayAbbrevRe.exec(string.slice(i));
					return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
				}

				function d3_time_parseWeekday(date, string, i) {
					d3_time_dayRe.lastIndex = 0;
					var n = d3_time_dayRe.exec(string.slice(i));
					return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
				}

				function d3_time_parseMonthAbbrev(date, string, i) {
					d3_time_monthAbbrevRe.lastIndex = 0;
					var n = d3_time_monthAbbrevRe.exec(string.slice(i));
					return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
				}

				function d3_time_parseMonth(date, string, i) {
					d3_time_monthRe.lastIndex = 0;
					var n = d3_time_monthRe.exec(string.slice(i));
					return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
				}

				function d3_time_parseLocaleFull(date, string, i) {
					return d3_time_parse(date, d3_time_formats.c.toString(), string, i);
				}

				function d3_time_parseLocaleDate(date, string, i) {
					return d3_time_parse(date, d3_time_formats.x.toString(), string, i);
				}

				function d3_time_parseLocaleTime(date, string, i) {
					return d3_time_parse(date, d3_time_formats.X.toString(), string, i);
				}

				function d3_time_parseAmPm(date, string, i) {
					var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase());
					return n == null ? -1 : (date.p = n, i);
				}
				return d3_time_format;
			}
			var d3_time_formatPads = {
					"-": "",
					_: " ",
					"0": "0"
				},
				d3_time_numberRe = /^\s*\d+/,
				d3_time_percentRe = /^%/;

			function d3_time_formatPad(value, fill, width) {
				var sign = value < 0 ? "-" : "",
					string = (sign ? -value : value) + "",
					length = string.length;
				return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string);
			}

			function d3_time_formatRe(names) {
				return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i");
			}

			function d3_time_formatLookup(names) {
				var map = new d3_Map(),
					i = -1,
					n = names.length;
				while(++i < n) map.set(names[i].toLowerCase(), i);
				return map;
			}

			function d3_time_parseWeekdayNumber(date, string, i) {
				d3_time_numberRe.lastIndex = 0;
				var n = d3_time_numberRe.exec(string.slice(i, i + 1));
				return n ? (date.w = +n[0], i + n[0].length) : -1;
			}

			function d3_time_parseWeekNumberSunday(date, string, i) {
				d3_time_numberRe.lastIndex = 0;
				var n = d3_time_numberRe.exec(string.slice(i));
				return n ? (date.U = +n[0], i + n[0].length) : -1;
			}

			function d3_time_parseWeekNumberMonday(date, string, i) {
				d3_time_numberRe.lastIndex = 0;
				var n = d3_time_numberRe.exec(string.slice(i));
				return n ? (date.W = +n[0], i + n[0].length) : -1;
			}

			function d3_time_parseFullYear(date, string, i) {
				d3_time_numberRe.lastIndex = 0;
				var n = d3_time_numberRe.exec(string.slice(i, i + 4));
				return n ? (date.y = +n[0], i + n[0].length) : -1;
			}

			function d3_time_parseYear(date, string, i) {
				d3_time_numberRe.lastIndex = 0;
				var n = d3_time_numberRe.exec(string.slice(i, i + 2));
				return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1;
			}

			function d3_time_parseZone(date, string, i) {
				return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string,
					i + 5) : -1;
			}

			function d3_time_expandYear(d) {
				return d + (d > 68 ? 1900 : 2e3);
			}

			function d3_time_parseMonthNumber(date, string, i) {
				d3_time_numberRe.lastIndex = 0;
				var n = d3_time_numberRe.exec(string.slice(i, i + 2));
				return n ? (date.m = n[0] - 1, i + n[0].length) : -1;
			}

			function d3_time_parseDay(date, string, i) {
				d3_time_numberRe.lastIndex = 0;
				var n = d3_time_numberRe.exec(string.slice(i, i + 2));
				return n ? (date.d = +n[0], i + n[0].length) : -1;
			}

			function d3_time_parseDayOfYear(date, string, i) {
				d3_time_numberRe.lastIndex = 0;
				var n = d3_time_numberRe.exec(string.slice(i, i + 3));
				return n ? (date.j = +n[0], i + n[0].length) : -1;
			}

			function d3_time_parseHour24(date, string, i) {
				d3_time_numberRe.lastIndex = 0;
				var n = d3_time_numberRe.exec(string.slice(i, i + 2));
				return n ? (date.H = +n[0], i + n[0].length) : -1;
			}

			function d3_time_parseMinutes(date, string, i) {
				d3_time_numberRe.lastIndex = 0;
				var n = d3_time_numberRe.exec(string.slice(i, i + 2));
				return n ? (date.M = +n[0], i + n[0].length) : -1;
			}

			function d3_time_parseSeconds(date, string, i) {
				d3_time_numberRe.lastIndex = 0;
				var n = d3_time_numberRe.exec(string.slice(i, i + 2));
				return n ? (date.S = +n[0], i + n[0].length) : -1;
			}

			function d3_time_parseMilliseconds(date, string, i) {
				d3_time_numberRe.lastIndex = 0;
				var n = d3_time_numberRe.exec(string.slice(i, i + 3));
				return n ? (date.L = +n[0], i + n[0].length) : -1;
			}

			function d3_time_zone(d) {
				var z = d.getTimezoneOffset(),
					zs = z > 0 ? "-" : "+",
					zh = abs(z) / 60 | 0,
					zm = abs(z) % 60;
				return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2);
			}

			function d3_time_parseLiteralPercent(date, string, i) {
				d3_time_percentRe.lastIndex = 0;
				var n = d3_time_percentRe.exec(string.slice(i, i + 1));
				return n ? i + n[0].length : -1;
			}

			function d3_time_formatMulti(formats) {
				var n = formats.length,
					i = -1;
				while(++i < n) formats[i][0] = this(formats[i][0]);
				return function(date) {
					var i = 0,
						f = formats[i];
					while(!f[1](date)) f = formats[++i];
					return f[0](date);
				};
			}
			d3.locale = function(locale) {
				return {
					numberFormat: d3_locale_numberFormat(locale),
					timeFormat: d3_locale_timeFormat(locale)
				};
			};
			var d3_locale_enUS = d3.locale({
				decimal: ".",
				thousands: ",",
				grouping: [3],
				currency: ["$", ""],
				dateTime: "%a %b %e %X %Y",
				date: "%m/%d/%Y",
				time: "%H:%M:%S",
				periods: ["AM", "PM"],
				days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
				shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
				months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
				shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
			});
			d3.format = d3_locale_enUS.numberFormat;
			d3.geo = {};

			function d3_adder() {}
			d3_adder.prototype = {
				s: 0,
				t: 0,
				add: function(y) {
					d3_adderSum(y, this.t, d3_adderTemp);
					d3_adderSum(d3_adderTemp.s, this.s, this);
					if(this.s) this.t += d3_adderTemp.t;
					else this.s = d3_adderTemp.t;
				},
				reset: function() {
					this.s = this.t = 0;
				},
				valueOf: function() {
					return this.s;
				}
			};
			var d3_adderTemp = new d3_adder();

			function d3_adderSum(a, b, o) {
				var x = o.s = a + b,
					bv = x - a,
					av = x - bv;
				o.t = a - av + (b - bv);
			}
			d3.geo.stream = function(object, listener) {
				if(object && d3_geo_streamObjectType.hasOwnProperty(object.type)) {
					d3_geo_streamObjectType[object.type](object, listener);
				} else {
					d3_geo_streamGeometry(object, listener);
				}
			};

			function d3_geo_streamGeometry(geometry, listener) {
				if(geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) {
					d3_geo_streamGeometryType[geometry.type](geometry, listener);
				}
			}
			var d3_geo_streamObjectType = {
				Feature: function(feature, listener) {
					d3_geo_streamGeometry(feature.geometry, listener);
				},
				FeatureCollection: function(object, listener) {
					var features = object.features,
						i = -1,
						n = features.length;
					while(++i < n) d3_geo_streamGeometry(features[i].geometry, listener);
				}
			};
			var d3_geo_streamGeometryType = {
				Sphere: function(object, listener) {
					listener.sphere();
				},
				Point: function(object, listener) {
					object = object.coordinates;
					listener.point(object[0], object[1], object[2]);
				},
				MultiPoint: function(object, listener) {
					var coordinates = object.coordinates,
						i = -1,
						n = coordinates.length;
					while(++i < n) object = coordinates[i], listener.point(object[0], object[1], object[2]);
				},
				LineString: function(object, listener) {
					d3_geo_streamLine(object.coordinates, listener, 0);
				},
				MultiLineString: function(object, listener) {
					var coordinates = object.coordinates,
						i = -1,
						n = coordinates.length;
					while(++i < n) d3_geo_streamLine(coordinates[i], listener, 0);
				},
				Polygon: function(object, listener) {
					d3_geo_streamPolygon(object.coordinates, listener);
				},
				MultiPolygon: function(object, listener) {
					var coordinates = object.coordinates,
						i = -1,
						n = coordinates.length;
					while(++i < n) d3_geo_streamPolygon(coordinates[i], listener);
				},
				GeometryCollection: function(object, listener) {
					var geometries = object.geometries,
						i = -1,
						n = geometries.length;
					while(++i < n) d3_geo_streamGeometry(geometries[i], listener);
				}
			};

			function d3_geo_streamLine(coordinates, listener, closed) {
				var i = -1,
					n = coordinates.length - closed,
					coordinate;
				listener.lineStart();
				while(++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1], coordinate[2]);
				listener.lineEnd();
			}

			function d3_geo_streamPolygon(coordinates, listener) {
				var i = -1,
					n = coordinates.length;
				listener.polygonStart();
				while(++i < n) d3_geo_streamLine(coordinates[i], listener, 1);
				listener.polygonEnd();
			}
			d3.geo.area = function(object) {
				d3_geo_areaSum = 0;
				d3.geo.stream(object, d3_geo_area);
				return d3_geo_areaSum;
			};
			var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder();
			var d3_geo_area = {
				sphere: function() {
					d3_geo_areaSum += 4 * π;
				},
				point: d3_noop,
				lineStart: d3_noop,
				lineEnd: d3_noop,
				polygonStart: function() {
					d3_geo_areaRingSum.reset();
					d3_geo_area.lineStart = d3_geo_areaRingStart;
				},
				polygonEnd: function() {
					var area = 2 * d3_geo_areaRingSum;
					d3_geo_areaSum += area < 0 ? 4 * π + area : area;
					d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop;
				}
			};

			function d3_geo_areaRingStart() {
				var λ00, φ00, λ0, cosφ0, sinφ0;
				d3_geo_area.point = function(λ, φ) {
					d3_geo_area.point = nextPoint;
					λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4),
						sinφ0 = Math.sin(φ);
				};

				function nextPoint(λ, φ) {
					λ *= d3_radians;
					φ = φ * d3_radians / 2 + π / 4;
					var dλ = λ - λ0,
						sdλ = dλ >= 0 ? 1 : -1,
						adλ = sdλ * dλ,
						cosφ = Math.cos(φ),
						sinφ = Math.sin(φ),
						k = sinφ0 * sinφ,
						u = cosφ0 * cosφ + k * Math.cos(adλ),
						v = k * sdλ * Math.sin(adλ);
					d3_geo_areaRingSum.add(Math.atan2(v, u));
					λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ;
				}
				d3_geo_area.lineEnd = function() {
					nextPoint(λ00, φ00);
				};
			}

			function d3_geo_cartesian(spherical) {
				var λ = spherical[0],
					φ = spherical[1],
					cosφ = Math.cos(φ);
				return [cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ)];
			}

			function d3_geo_cartesianDot(a, b) {
				return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
			}

			function d3_geo_cartesianCross(a, b) {
				return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
			}

			function d3_geo_cartesianAdd(a, b) {
				a[0] += b[0];
				a[1] += b[1];
				a[2] += b[2];
			}

			function d3_geo_cartesianScale(vector, k) {
				return [vector[0] * k, vector[1] * k, vector[2] * k];
			}

			function d3_geo_cartesianNormalize(d) {
				var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
				d[0] /= l;
				d[1] /= l;
				d[2] /= l;
			}

			function d3_geo_spherical(cartesian) {
				return [Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2])];
			}

			function d3_geo_sphericalEqual(a, b) {
				return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε;
			}
			d3.geo.bounds = function() {
				var λ0, φ0, λ1, φ1, λ_, λ__, φ__, p0, dλSum, ranges, range;
				var bound = {
					point: point,
					lineStart: lineStart,
					lineEnd: lineEnd,
					polygonStart: function() {
						bound.point = ringPoint;
						bound.lineStart = ringStart;
						bound.lineEnd = ringEnd;
						dλSum = 0;
						d3_geo_area.polygonStart();
					},
					polygonEnd: function() {
						d3_geo_area.polygonEnd();
						bound.point = point;
						bound.lineStart = lineStart;
						bound.lineEnd = lineEnd;
						if(d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90);
						else if(dλSum > ε) φ1 = 90;
						else if(dλSum < -ε) φ0 = -90;
						range[0] = λ0, range[1] = λ1;
					}
				};

				function point(λ, φ) {
					ranges.push(range = [λ0 = λ, λ1 = λ]);
					if(φ < φ0) φ0 = φ;
					if(φ > φ1) φ1 = φ;
				}

				function linePoint(λ, φ) {
					var p = d3_geo_cartesian([λ * d3_radians, φ * d3_radians]);
					if(p0) {
						var normal = d3_geo_cartesianCross(p0, p),
							equatorial = [normal[1], -normal[0], 0],
							inflection = d3_geo_cartesianCross(equatorial, normal);
						d3_geo_cartesianNormalize(inflection);
						inflection = d3_geo_spherical(inflection);
						var dλ = λ - λ_,
							s = dλ > 0 ? 1 : -1,
							λi = inflection[0] * d3_degrees * s,
							antimeridian = abs(dλ) > 180;
						if(antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
							var φi = inflection[1] * d3_degrees;
							if(φi > φ1) φ1 = φi;
						} else if(λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
							var φi = -inflection[1] * d3_degrees;
							if(φi < φ0) φ0 = φi;
						} else {
							if(φ < φ0) φ0 = φ;
							if(φ > φ1) φ1 = φ;
						}
						if(antimeridian) {
							if(λ < λ_) {
								if(angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
							} else {
								if(angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
							}
						} else {
							if(λ1 >= λ0) {
								if(λ < λ0) λ0 = λ;
								if(λ > λ1) λ1 = λ;
							} else {
								if(λ > λ_) {
									if(angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
								} else {
									if(angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
								}
							}
						}
					} else {
						point(λ, φ);
					}
					p0 = p, λ_ = λ;
				}

				function lineStart() {
					bound.point = linePoint;
				}

				function lineEnd() {
					range[0] = λ0, range[1] = λ1;
					bound.point = point;
					p0 = null;
				}

				function ringPoint(λ, φ) {
					if(p0) {
						var dλ = λ - λ_;
						dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ;
					} else λ__ = λ, φ__ = φ;
					d3_geo_area.point(λ, φ);
					linePoint(λ, φ);
				}

				function ringStart() {
					d3_geo_area.lineStart();
				}

				function ringEnd() {
					ringPoint(λ__, φ__);
					d3_geo_area.lineEnd();
					if(abs(dλSum) > ε) λ0 = -(λ1 = 180);
					range[0] = λ0, range[1] = λ1;
					p0 = null;
				}

				function angle(λ0, λ1) {
					return(λ1 -= λ0) < 0 ? λ1 + 360 : λ1;
				}

				function compareRanges(a, b) {
					return a[0] - b[0];
				}

				function withinRange(x, range) {
					return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x;
				}
				return function(feature) {
					φ1 = λ1 = -(λ0 = φ0 = Infinity);
					ranges = [];
					d3.geo.stream(feature, bound);
					var n = ranges.length;
					if(n) {
						ranges.sort(compareRanges);
						for(var i = 1, a = ranges[0], b, merged = [a]; i < n; ++i) {
							b = ranges[i];
							if(withinRange(b[0], a) || withinRange(b[1], a)) {
								if(angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1];
								if(angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0];
							} else {
								merged.push(a = b);
							}
						}
						var best = -Infinity,
							dλ;
						for(var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) {
							b = merged[i];
							if((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1];
						}
					}
					ranges = range = null;
					return λ0 === Infinity || φ0 === Infinity ? [
						[NaN, NaN],
						[NaN, NaN]
					] : [
						[λ0, φ0],
						[λ1, φ1]
					];
				};
			}();
			d3.geo.centroid = function(object) {
				d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
				d3.geo.stream(object, d3_geo_centroid);
				var x = d3_geo_centroidX2,
					y = d3_geo_centroidY2,
					z = d3_geo_centroidZ2,
					m = x * x + y * y + z * z;
				if(m < ε2) {
					x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1;
					if(d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0;
					m = x * x + y * y + z * z;
					if(m < ε2) return [NaN, NaN];
				}
				return [Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees];
			};
			var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2;
			var d3_geo_centroid = {
				sphere: d3_noop,
				point: d3_geo_centroidPoint,
				lineStart: d3_geo_centroidLineStart,
				lineEnd: d3_geo_centroidLineEnd,
				polygonStart: function() {
					d3_geo_centroid.lineStart = d3_geo_centroidRingStart;
				},
				polygonEnd: function() {
					d3_geo_centroid.lineStart = d3_geo_centroidLineStart;
				}
			};

			function d3_geo_centroidPoint(λ, φ) {
				λ *= d3_radians;
				var cosφ = Math.cos(φ *= d3_radians);
				d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ));
			}

			function d3_geo_centroidPointXYZ(x, y, z) {
				++d3_geo_centroidW0;
				d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0;
				d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0;
				d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0;
			}

			function d3_geo_centroidLineStart() {
				var x0, y0, z0;
				d3_geo_centroid.point = function(λ, φ) {
					λ *= d3_radians;
					var cosφ = Math.cos(φ *= d3_radians);
					x0 = cosφ * Math.cos(λ);
					y0 = cosφ * Math.sin(λ);
					z0 = Math.sin(φ);
					d3_geo_centroid.point = nextPoint;
					d3_geo_centroidPointXYZ(x0, y0, z0);
				};

				function nextPoint(λ, φ) {
					λ *= d3_radians;
					var cosφ = Math.cos(φ *= d3_radians),
						x = cosφ * Math.cos(λ),
						y = cosφ * Math.sin(λ),
						z = Math.sin(φ),
						w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z);
					d3_geo_centroidW1 += w;
					d3_geo_centroidX1 += w * (x0 + (x0 = x));
					d3_geo_centroidY1 += w * (y0 + (y0 = y));
					d3_geo_centroidZ1 += w * (z0 + (z0 = z));
					d3_geo_centroidPointXYZ(x0, y0, z0);
				}
			}

			function d3_geo_centroidLineEnd() {
				d3_geo_centroid.point = d3_geo_centroidPoint;
			}

			function d3_geo_centroidRingStart() {
				var λ00, φ00, x0, y0, z0;
				d3_geo_centroid.point = function(λ, φ) {
					λ00 = λ, φ00 = φ;
					d3_geo_centroid.point = nextPoint;
					λ *= d3_radians;
					var cosφ = Math.cos(φ *= d3_radians);
					x0 = cosφ * Math.cos(λ);
					y0 = cosφ * Math.sin(λ);
					z0 = Math.sin(φ);
					d3_geo_centroidPointXYZ(x0, y0, z0);
				};
				d3_geo_centroid.lineEnd = function() {
					nextPoint(λ00, φ00);
					d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd;
					d3_geo_centroid.point = d3_geo_centroidPoint;
				};

				function nextPoint(λ, φ) {
					λ *= d3_radians;
					var cosφ = Math.cos(φ *= d3_radians),
						x = cosφ * Math.cos(λ),
						y = cosφ * Math.sin(λ),
						z = Math.sin(φ),
						cx = y0 * z - z0 * y,
						cy = z0 * x - x0 * z,
						cz = x0 * y - y0 * x,
						m = Math.sqrt(cx * cx + cy * cy + cz * cz),
						u = x0 * x + y0 * y + z0 * z,
						v = m && -d3_acos(u) / m,
						w = Math.atan2(m, u);
					d3_geo_centroidX2 += v * cx;
					d3_geo_centroidY2 += v * cy;
					d3_geo_centroidZ2 += v * cz;
					d3_geo_centroidW1 += w;
					d3_geo_centroidX1 += w * (x0 + (x0 = x));
					d3_geo_centroidY1 += w * (y0 + (y0 = y));
					d3_geo_centroidZ1 += w * (z0 + (z0 = z));
					d3_geo_centroidPointXYZ(x0, y0, z0);
				}
			}

			function d3_geo_compose(a, b) {
				function compose(x, y) {
					return x = a(x, y), b(x[0], x[1]);
				}
				if(a.invert && b.invert) compose.invert = function(x, y) {
					return x = b.invert(x, y), x && a.invert(x[0], x[1]);
				};
				return compose;
			}

			function d3_true() {
				return true;
			}

			function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) {
				var subject = [],
					clip = [];
				segments.forEach(function(segment) {
					if((n = segment.length - 1) <= 0) return;
					var n, p0 = segment[0],
						p1 = segment[n];
					if(d3_geo_sphericalEqual(p0, p1)) {
						listener.lineStart();
						for(var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]);
						listener.lineEnd();
						return;
					}
					var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true),
						b = new d3_geo_clipPolygonIntersection(p0, null, a, false);
					a.o = b;
					subject.push(a);
					clip.push(b);
					a = new d3_geo_clipPolygonIntersection(p1, segment, null, false);
					b = new d3_geo_clipPolygonIntersection(p1, null, a, true);
					a.o = b;
					subject.push(a);
					clip.push(b);
				});
				clip.sort(compare);
				d3_geo_clipPolygonLinkCircular(subject);
				d3_geo_clipPolygonLinkCircular(clip);
				if(!subject.length) return;
				for(var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) {
					clip[i].e = entry = !entry;
				}
				var start = subject[0],
					points, point;
				while(1) {
					var current = start,
						isSubject = true;
					while(current.v)
						if((current = current.n) === start) return;
					points = current.z;
					listener.lineStart();
					do {
						current.v = current.o.v = true;
						if(current.e) {
							if(isSubject) {
								for(var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]);
							} else {
								interpolate(current.x, current.n.x, 1, listener);
							}
							current = current.n;
						} else {
							if(isSubject) {
								points = current.p.z;
								for(var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]);
							} else {
								interpolate(current.x, current.p.x, -1, listener);
							}
							current = current.p;
						}
						current = current.o;
						points = current.z;
						isSubject = !isSubject;
					} while (!current.v);
					listener.lineEnd();
				}
			}

			function d3_geo_clipPolygonLinkCircular(array) {
				if(!(n = array.length)) return;
				var n, i = 0,
					a = array[0],
					b;
				while(++i < n) {
					a.n = b = array[i];
					b.p = a;
					a = b;
				}
				a.n = b = array[0];
				b.p = a;
			}

			function d3_geo_clipPolygonIntersection(point, points, other, entry) {
				this.x = point;
				this.z = points;
				this.o = other;
				this.e = entry;
				this.v = false;
				this.n = this.p = null;
			}

			function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) {
				return function(rotate, listener) {
					var line = clipLine(listener),
						rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]);
					var clip = {
						point: point,
						lineStart: lineStart,
						lineEnd: lineEnd,
						polygonStart: function() {
							clip.point = pointRing;
							clip.lineStart = ringStart;
							clip.lineEnd = ringEnd;
							segments = [];
							polygon = [];
						},
						polygonEnd: function() {
							clip.point = point;
							clip.lineStart = lineStart;
							clip.lineEnd = lineEnd;
							segments = d3.merge(segments);
							var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon);
							if(segments.length) {
								if(!polygonStarted) listener.polygonStart(), polygonStarted = true;
								d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener);
							} else if(clipStartInside) {
								if(!polygonStarted) listener.polygonStart(), polygonStarted = true;
								listener.lineStart();
								interpolate(null, null, 1, listener);
								listener.lineEnd();
							}
							if(polygonStarted) listener.polygonEnd(), polygonStarted = false;
							segments = polygon = null;
						},
						sphere: function() {
							listener.polygonStart();
							listener.lineStart();
							interpolate(null, null, 1, listener);
							listener.lineEnd();
							listener.polygonEnd();
						}
					};

					function point(λ, φ) {
						var point = rotate(λ, φ);
						if(pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ);
					}

					function pointLine(λ, φ) {
						var point = rotate(λ, φ);
						line.point(point[0], point[1]);
					}

					function lineStart() {
						clip.point = pointLine;
						line.lineStart();
					}

					function lineEnd() {
						clip.point = point;
						line.lineEnd();
					}
					var segments;
					var buffer = d3_geo_clipBufferListener(),
						ringListener = clipLine(buffer),
						polygonStarted = false,
						polygon, ring;

					function pointRing(λ, φ) {
						ring.push([λ, φ]);
						var point = rotate(λ, φ);
						ringListener.point(point[0], point[1]);
					}

					function ringStart() {
						ringListener.lineStart();
						ring = [];
					}

					function ringEnd() {
						pointRing(ring[0][0], ring[0][1]);
						ringListener.lineEnd();
						var clean = ringListener.clean(),
							ringSegments = buffer.buffer(),
							segment, n = ringSegments.length;
						ring.pop();
						polygon.push(ring);
						ring = null;
						if(!n) return;
						if(clean & 1) {
							segment = ringSegments[0];
							var n = segment.length - 1,
								i = -1,
								point;
							if(n > 0) {
								if(!polygonStarted) listener.polygonStart(), polygonStarted = true;
								listener.lineStart();
								while(++i < n) listener.point((point = segment[i])[0], point[1]);
								listener.lineEnd();
							}
							return;
						}
						if(n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
						segments.push(ringSegments.filter(d3_geo_clipSegmentLength1));
					}
					return clip;
				};
			}

			function d3_geo_clipSegmentLength1(segment) {
				return segment.length > 1;
			}

			function d3_geo_clipBufferListener() {
				var lines = [],
					line;
				return {
					lineStart: function() {
						lines.push(line = []);
					},
					point: function(λ, φ) {
						line.push([λ, φ]);
					},
					lineEnd: d3_noop,
					buffer: function() {
						var buffer = lines;
						lines = [];
						line = null;
						return buffer;
					},
					rejoin: function() {
						if(lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
					}
				};
			}

			function d3_geo_clipSort(a, b) {
				return((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]);
			}
			var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [-π, -π / 2]);

			function d3_geo_clipAntimeridianLine(listener) {
				var λ0 = NaN,
					φ0 = NaN,
					sλ0 = NaN,
					clean;
				return {
					lineStart: function() {
						listener.lineStart();
						clean = 1;
					},
					point: function(λ1, φ1) {
						var sλ1 = λ1 > 0 ? π : -π,
							dλ = abs(λ1 - λ0);
						if(abs(dλ - π) < ε) {
							listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? halfπ : -halfπ);
							listener.point(sλ0, φ0);
							listener.lineEnd();
							listener.lineStart();
							listener.point(sλ1, φ0);
							listener.point(λ1, φ0);
							clean = 0;
						} else if(sλ0 !== sλ1 && dλ >= π) {
							if(abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε;
							if(abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε;
							φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1);
							listener.point(sλ0, φ0);
							listener.lineEnd();
							listener.lineStart();
							listener.point(sλ1, φ0);
							clean = 0;
						}
						listener.point(λ0 = λ1, φ0 = φ1);
						sλ0 = sλ1;
					},
					lineEnd: function() {
						listener.lineEnd();
						λ0 = φ0 = NaN;
					},
					clean: function() {
						return 2 - clean;
					}
				};
			}

			function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) {
				var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1);
				return abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2;
			}

			function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) {
				var φ;
				if(from == null) {
					φ = direction * halfπ;
					listener.point(-π, φ);
					listener.point(0, φ);
					listener.point(π, φ);
					listener.point(π, 0);
					listener.point(π, -φ);
					listener.point(0, -φ);
					listener.point(-π, -φ);
					listener.point(-π, 0);
					listener.point(-π, φ);
				} else if(abs(from[0] - to[0]) > ε) {
					var s = from[0] < to[0] ? π : -π;
					φ = direction * s / 2;
					listener.point(-s, φ);
					listener.point(0, φ);
					listener.point(s, φ);
				} else {
					listener.point(to[0], to[1]);
				}
			}

			function d3_geo_pointInPolygon(point, polygon) {
				var meridian = point[0],
					parallel = point[1],
					meridianNormal = [Math.sin(meridian), -Math.cos(meridian), 0],
					polarAngle = 0,
					winding = 0;
				d3_geo_areaRingSum.reset();
				for(var i = 0, n = polygon.length; i < n; ++i) {
					var ring = polygon[i],
						m = ring.length;
					if(!m) continue;
					var point0 = ring[0],
						λ0 = point0[0],
						φ0 = point0[1] / 2 + π / 4,
						sinφ0 = Math.sin(φ0),
						cosφ0 = Math.cos(φ0),
						j = 1;
					while(true) {
						if(j === m) j = 0;
						point = ring[j];
						var λ = point[0],
							φ = point[1] / 2 + π / 4,
							sinφ = Math.sin(φ),
							cosφ = Math.cos(φ),
							dλ = λ - λ0,
							sdλ = dλ >= 0 ? 1 : -1,
							adλ = sdλ * dλ,
							antimeridian = adλ > π,
							k = sinφ0 * sinφ;
						d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ)));
						polarAngle += antimeridian ? dλ + sdλ * τ : dλ;
						if(antimeridian ^ λ0 >= meridian ^ λ >= meridian) {
							var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point));
							d3_geo_cartesianNormalize(arc);
							var intersection = d3_geo_cartesianCross(meridianNormal, arc);
							d3_geo_cartesianNormalize(intersection);
							var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]);
							if(parallel > φarc || parallel === φarc && (arc[0] || arc[1])) {
								winding += antimeridian ^ dλ >= 0 ? 1 : -1;
							}
						}
						if(!j++) break;
						λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point;
					}
				}
				return(polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < 0) ^ winding & 1;
			}

			function d3_geo_clipCircle(radius) {
				var cr = Math.cos(radius),
					smallRadius = cr > 0,
					notHemisphere = abs(cr) > ε,
					interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians);
				return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [0, -radius] : [-π, radius - π]);

				function visible(λ, φ) {
					return Math.cos(λ) * Math.cos(φ) > cr;
				}

				function clipLine(listener) {
					var point0, c0, v0, v00, clean;
					return {
						lineStart: function() {
							v00 = v0 = false;
							clean = 1;
						},
						point: function(λ, φ) {
							var point1 = [λ, φ],
								point2, v = visible(λ, φ),
								c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0;
							if(!point0 && (v00 = v0 = v)) listener.lineStart();
							if(v !== v0) {
								point2 = intersect(point0, point1);
								if(d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) {
									point1[0] += ε;
									point1[1] += ε;
									v = visible(point1[0], point1[1]);
								}
							}
							if(v !== v0) {
								clean = 0;
								if(v) {
									listener.lineStart();
									point2 = intersect(point1, point0);
									listener.point(point2[0], point2[1]);
								} else {
									point2 = intersect(point0, point1);
									listener.point(point2[0], point2[1]);
									listener.lineEnd();
								}
								point0 = point2;
							} else if(notHemisphere && point0 && smallRadius ^ v) {
								var t;
								if(!(c & c0) && (t = intersect(point1, point0, true))) {
									clean = 0;
									if(smallRadius) {
										listener.lineStart();
										listener.point(t[0][0], t[0][1]);
										listener.point(t[1][0], t[1][1]);
										listener.lineEnd();
									} else {
										listener.point(t[1][0], t[1][1]);
										listener.lineEnd();
										listener.lineStart();
										listener.point(t[0][0], t[0][1]);
									}
								}
							}
							if(v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) {
								listener.point(point1[0], point1[1]);
							}
							point0 = point1, v0 = v, c0 = c;
						},
						lineEnd: function() {
							if(v0) listener.lineEnd();
							point0 = null;
						},
						clean: function() {
							return clean | (v00 && v0) << 1;
						}
					};
				}

				function intersect(a, b, two) {
					var pa = d3_geo_cartesian(a),
						pb = d3_geo_cartesian(b);
					var n1 = [1, 0, 0],
						n2 = d3_geo_cartesianCross(pa, pb),
						n2n2 = d3_geo_cartesianDot(n2, n2),
						n1n2 = n2[0],
						determinant = n2n2 - n1n2 * n1n2;
					if(!determinant) return !two && a;
					var c1 = cr * n2n2 / determinant,
						c2 = -cr * n1n2 / determinant,
						n1xn2 = d3_geo_cartesianCross(n1, n2),
						A = d3_geo_cartesianScale(n1, c1),
						B = d3_geo_cartesianScale(n2, c2);
					d3_geo_cartesianAdd(A, B);
					var u = n1xn2,
						w = d3_geo_cartesianDot(A, u),
						uu = d3_geo_cartesianDot(u, u),
						t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1);
					if(t2 < 0) return;
					var t = Math.sqrt(t2),
						q = d3_geo_cartesianScale(u, (-w - t) / uu);
					d3_geo_cartesianAdd(q, A);
					q = d3_geo_spherical(q);
					if(!two) return q;
					var λ0 = a[0],
						λ1 = b[0],
						φ0 = a[1],
						φ1 = b[1],
						z;
					if(λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z;
					var δλ = λ1 - λ0,
						polar = abs(δλ - π) < ε,
						meridian = polar || δλ < ε;
					if(!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z;
					if(meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) {
						var q1 = d3_geo_cartesianScale(u, (-w + t) / uu);
						d3_geo_cartesianAdd(q1, A);
						return [q, d3_geo_spherical(q1)];
					}
				}

				function code(λ, φ) {
					var r = smallRadius ? radius : π - radius,
						code = 0;
					if(λ < -r) code |= 1;
					else if(λ > r) code |= 2;
					if(φ < -r) code |= 4;
					else if(φ > r) code |= 8;
					return code;
				}
			}

			function d3_geom_clipLine(x0, y0, x1, y1) {
				return function(line) {
					var a = line.a,
						b = line.b,
						ax = a.x,
						ay = a.y,
						bx = b.x,
						by = b.y,
						t0 = 0,
						t1 = 1,
						dx = bx - ax,
						dy = by - ay,
						r;
					r = x0 - ax;
					if(!dx && r > 0) return;
					r /= dx;
					if(dx < 0) {
						if(r < t0) return;
						if(r < t1) t1 = r;
					} else if(dx > 0) {
						if(r > t1) return;
						if(r > t0) t0 = r;
					}
					r = x1 - ax;
					if(!dx && r < 0) return;
					r /= dx;
					if(dx < 0) {
						if(r > t1) return;
						if(r > t0) t0 = r;
					} else if(dx > 0) {
						if(r < t0) return;
						if(r < t1) t1 = r;
					}
					r = y0 - ay;
					if(!dy && r > 0) return;
					r /= dy;
					if(dy < 0) {
						if(r < t0) return;
						if(r < t1) t1 = r;
					} else if(dy > 0) {
						if(r > t1) return;
						if(r > t0) t0 = r;
					}
					r = y1 - ay;
					if(!dy && r < 0) return;
					r /= dy;
					if(dy < 0) {
						if(r > t1) return;
						if(r > t0) t0 = r;
					} else if(dy > 0) {
						if(r < t0) return;
						if(r < t1) t1 = r;
					}
					if(t0 > 0) line.a = {
						x: ax + t0 * dx,
						y: ay + t0 * dy
					};
					if(t1 < 1) line.b = {
						x: ax + t1 * dx,
						y: ay + t1 * dy
					};
					return line;
				};
			}
			var d3_geo_clipExtentMAX = 1e9;
			d3.geo.clipExtent = function() {
				var x0, y0, x1, y1, stream, clip, clipExtent = {
					stream: function(output) {
						if(stream) stream.valid = false;
						stream = clip(output);
						stream.valid = true;
						return stream;
					},
					extent: function(_) {
						if(!arguments.length) return [
							[x0, y0],
							[x1, y1]
						];
						clip = d3_geo_clipExtent(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]);
						if(stream) stream.valid = false, stream = null;
						return clipExtent;
					}
				};
				return clipExtent.extent([
					[0, 0],
					[960, 500]
				]);
			};

			function d3_geo_clipExtent(x0, y0, x1, y1) {
				return function(listener) {
					var listener_ = listener,
						bufferListener = d3_geo_clipBufferListener(),
						clipLine = d3_geom_clipLine(x0, y0, x1, y1),
						segments, polygon, ring;
					var clip = {
						point: point,
						lineStart: lineStart,
						lineEnd: lineEnd,
						polygonStart: function() {
							listener = bufferListener;
							segments = [];
							polygon = [];
							clean = true;
						},
						polygonEnd: function() {
							listener = listener_;
							segments = d3.merge(segments);
							var clipStartInside = insidePolygon([x0, y1]),
								inside = clean && clipStartInside,
								visible = segments.length;
							if(inside || visible) {
								listener.polygonStart();
								if(inside) {
									listener.lineStart();
									interpolate(null, null, 1, listener);
									listener.lineEnd();
								}
								if(visible) {
									d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener);
								}
								listener.polygonEnd();
							}
							segments = polygon = ring = null;
						}
					};

					function insidePolygon(p) {
						var wn = 0,
							n = polygon.length,
							y = p[1];
						for(var i = 0; i < n; ++i) {
							for(var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) {
								b = v[j];
								if(a[1] <= y) {
									if(b[1] > y && d3_cross2d(a, b, p) > 0) ++wn;
								} else {
									if(b[1] <= y && d3_cross2d(a, b, p) < 0) --wn;
								}
								a = b;
							}
						}
						return wn !== 0;
					}

					function interpolate(from, to, direction, listener) {
						var a = 0,
							a1 = 0;
						if(from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) {
							do {
								listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0);
							} while ((a = (a + direction + 4) % 4) !== a1);
						} else {
							listener.point(to[0], to[1]);
						}
					}

					function pointVisible(x, y) {
						return x0 <= x && x <= x1 && y0 <= y && y <= y1;
					}

					function point(x, y) {
						if(pointVisible(x, y)) listener.point(x, y);
					}
					var x__, y__, v__, x_, y_, v_, first, clean;

					function lineStart() {
						clip.point = linePoint;
						if(polygon) polygon.push(ring = []);
						first = true;
						v_ = false;
						x_ = y_ = NaN;
					}

					function lineEnd() {
						if(segments) {
							linePoint(x__, y__);
							if(v__ && v_) bufferListener.rejoin();
							segments.push(bufferListener.buffer());
						}
						clip.point = point;
						if(v_) listener.lineEnd();
					}

					function linePoint(x, y) {
						x = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, x));
						y = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, y));
						var v = pointVisible(x, y);
						if(polygon) ring.push([x, y]);
						if(first) {
							x__ = x, y__ = y, v__ = v;
							first = false;
							if(v) {
								listener.lineStart();
								listener.point(x, y);
							}
						} else {
							if(v && v_) listener.point(x, y);
							else {
								var l = {
									a: {
										x: x_,
										y: y_
									},
									b: {
										x: x,
										y: y
									}
								};
								if(clipLine(l)) {
									if(!v_) {
										listener.lineStart();
										listener.point(l.a.x, l.a.y);
									}
									listener.point(l.b.x, l.b.y);
									if(!v) listener.lineEnd();
									clean = false;
								} else if(v) {
									listener.lineStart();
									listener.point(x, y);
									clean = false;
								}
							}
						}
						x_ = x, y_ = y, v_ = v;
					}
					return clip;
				};

				function corner(p, direction) {
					return abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2;
				}

				function compare(a, b) {
					return comparePoints(a.x, b.x);
				}

				function comparePoints(a, b) {
					var ca = corner(a, 1),
						cb = corner(b, 1);
					return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0];
				}
			}

			function d3_geo_conic(projectAt) {
				var φ0 = 0,
					φ1 = π / 3,
					m = d3_geo_projectionMutator(projectAt),
					p = m(φ0, φ1);
				p.parallels = function(_) {
					if(!arguments.length) return [φ0 / π * 180, φ1 / π * 180];
					return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180);
				};
				return p;
			}

			function d3_geo_conicEqualArea(φ0, φ1) {
				var sinφ0 = Math.sin(φ0),
					n = (sinφ0 + Math.sin(φ1)) / 2,
					C = 1 + sinφ0 * (2 * n - sinφ0),
					ρ0 = Math.sqrt(C) / n;

				function forward(λ, φ) {
					var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n;
					return [ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ)];
				}
				forward.invert = function(x, y) {
					var ρ0_y = ρ0 - y;
					return [Math.atan2(x, ρ0_y) / n, d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n))];
				};
				return forward;
			}
			(d3.geo.conicEqualArea = function() {
				return d3_geo_conic(d3_geo_conicEqualArea);
			}).raw = d3_geo_conicEqualArea;
			d3.geo.albers = function() {
				return d3.geo.conicEqualArea().rotate([96, 0]).center([-.6, 38.7]).parallels([29.5, 45.5]).scale(1070);
			};
			d3.geo.albersUsa = function() {
				var lower48 = d3.geo.albers();
				var alaska = d3.geo.conicEqualArea().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
				var hawaii = d3.geo.conicEqualArea().rotate([157, 0]).center([-3, 19.9]).parallels([8, 18]);
				var point, pointStream = {
						point: function(x, y) {
							point = [x, y];
						}
					},
					lower48Point, alaskaPoint, hawaiiPoint;

				function albersUsa(coordinates) {
					var x = coordinates[0],
						y = coordinates[1];
					point = null;
					(lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y);
					return point;
				}
				albersUsa.invert = function(coordinates) {
					var k = lower48.scale(),
						t = lower48.translate(),
						x = (coordinates[0] - t[0]) / k,
						y = (coordinates[1] - t[1]) / k;
					return(y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates);
				};
				albersUsa.stream = function(stream) {
					var lower48Stream = lower48.stream(stream),
						alaskaStream = alaska.stream(stream),
						hawaiiStream = hawaii.stream(stream);
					return {
						point: function(x, y) {
							lower48Stream.point(x, y);
							alaskaStream.point(x, y);
							hawaiiStream.point(x, y);
						},
						sphere: function() {
							lower48Stream.sphere();
							alaskaStream.sphere();
							hawaiiStream.sphere();
						},
						lineStart: function() {
							lower48Stream.lineStart();
							alaskaStream.lineStart();
							hawaiiStream.lineStart();
						},
						lineEnd: function() {
							lower48Stream.lineEnd();
							alaskaStream.lineEnd();
							hawaiiStream.lineEnd();
						},
						polygonStart: function() {
							lower48Stream.polygonStart();
							alaskaStream.polygonStart();
							hawaiiStream.polygonStart();
						},
						polygonEnd: function() {
							lower48Stream.polygonEnd();
							alaskaStream.polygonEnd();
							hawaiiStream.polygonEnd();
						}
					};
				};
				albersUsa.precision = function(_) {
					if(!arguments.length) return lower48.precision();
					lower48.precision(_);
					alaska.precision(_);
					hawaii.precision(_);
					return albersUsa;
				};
				albersUsa.scale = function(_) {
					if(!arguments.length) return lower48.scale();
					lower48.scale(_);
					alaska.scale(_ * .35);
					hawaii.scale(_);
					return albersUsa.translate(lower48.translate());
				};
				albersUsa.translate = function(_) {
					if(!arguments.length) return lower48.translate();
					var k = lower48.scale(),
						x = +_[0],
						y = +_[1];
					lower48Point = lower48.translate(_).clipExtent([
						[x - .455 * k, y - .238 * k],
						[x + .455 * k, y + .238 * k]
					]).stream(pointStream).point;
					alaskaPoint = alaska.translate([x - .307 * k, y + .201 * k]).clipExtent([
						[x - .425 * k + ε, y + .12 * k + ε],
						[x - .214 * k - ε, y + .234 * k - ε]
					]).stream(pointStream).point;
					hawaiiPoint = hawaii.translate([x - .205 * k, y + .212 * k]).clipExtent([
						[x - .214 * k + ε, y + .166 * k + ε],
						[x - .115 * k - ε, y + .234 * k - ε]
					]).stream(pointStream).point;
					return albersUsa;
				};
				return albersUsa.scale(1070);
			};
			var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = {
				point: d3_noop,
				lineStart: d3_noop,
				lineEnd: d3_noop,
				polygonStart: function() {
					d3_geo_pathAreaPolygon = 0;
					d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart;
				},
				polygonEnd: function() {
					d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop;
					d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2);
				}
			};

			function d3_geo_pathAreaRingStart() {
				var x00, y00, x0, y0;
				d3_geo_pathArea.point = function(x, y) {
					d3_geo_pathArea.point = nextPoint;
					x00 = x0 = x, y00 = y0 = y;
				};

				function nextPoint(x, y) {
					d3_geo_pathAreaPolygon += y0 * x - x0 * y;
					x0 = x, y0 = y;
				}
				d3_geo_pathArea.lineEnd = function() {
					nextPoint(x00, y00);
				};
			}
			var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1;
			var d3_geo_pathBounds = {
				point: d3_geo_pathBoundsPoint,
				lineStart: d3_noop,
				lineEnd: d3_noop,
				polygonStart: d3_noop,
				polygonEnd: d3_noop
			};

			function d3_geo_pathBoundsPoint(x, y) {
				if(x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x;
				if(x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x;
				if(y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y;
				if(y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y;
			}

			function d3_geo_pathBuffer() {
				var pointCircle = d3_geo_pathBufferCircle(4.5),
					buffer = [];
				var stream = {
					point: point,
					lineStart: function() {
						stream.point = pointLineStart;
					},
					lineEnd: lineEnd,
					polygonStart: function() {
						stream.lineEnd = lineEndPolygon;
					},
					polygonEnd: function() {
						stream.lineEnd = lineEnd;
						stream.point = point;
					},
					pointRadius: function(_) {
						pointCircle = d3_geo_pathBufferCircle(_);
						return stream;
					},
					result: function() {
						if(buffer.length) {
							var result = buffer.join("");
							buffer = [];
							return result;
						}
					}
				};

				function point(x, y) {
					buffer.push("M", x, ",", y, pointCircle);
				}

				function pointLineStart(x, y) {
					buffer.push("M", x, ",", y);
					stream.point = pointLine;
				}

				function pointLine(x, y) {
					buffer.push("L", x, ",", y);
				}

				function lineEnd() {
					stream.point = point;
				}

				function lineEndPolygon() {
					buffer.push("Z");
				}
				return stream;
			}

			function d3_geo_pathBufferCircle(radius) {
				return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z";
			}
			var d3_geo_pathCentroid = {
				point: d3_geo_pathCentroidPoint,
				lineStart: d3_geo_pathCentroidLineStart,
				lineEnd: d3_geo_pathCentroidLineEnd,
				polygonStart: function() {
					d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart;
				},
				polygonEnd: function() {
					d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
					d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart;
					d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd;
				}
			};

			function d3_geo_pathCentroidPoint(x, y) {
				d3_geo_centroidX0 += x;
				d3_geo_centroidY0 += y;
				++d3_geo_centroidZ0;
			}

			function d3_geo_pathCentroidLineStart() {
				var x0, y0;
				d3_geo_pathCentroid.point = function(x, y) {
					d3_geo_pathCentroid.point = nextPoint;
					d3_geo_pathCentroidPoint(x0 = x, y0 = y);
				};

				function nextPoint(x, y) {
					var dx = x - x0,
						dy = y - y0,
						z = Math.sqrt(dx * dx + dy * dy);
					d3_geo_centroidX1 += z * (x0 + x) / 2;
					d3_geo_centroidY1 += z * (y0 + y) / 2;
					d3_geo_centroidZ1 += z;
					d3_geo_pathCentroidPoint(x0 = x, y0 = y);
				}
			}

			function d3_geo_pathCentroidLineEnd() {
				d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
			}

			function d3_geo_pathCentroidRingStart() {
				var x00, y00, x0, y0;
				d3_geo_pathCentroid.point = function(x, y) {
					d3_geo_pathCentroid.point = nextPoint;
					d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y);
				};

				function nextPoint(x, y) {
					var dx = x - x0,
						dy = y - y0,
						z = Math.sqrt(dx * dx + dy * dy);
					d3_geo_centroidX1 += z * (x0 + x) / 2;
					d3_geo_centroidY1 += z * (y0 + y) / 2;
					d3_geo_centroidZ1 += z;
					z = y0 * x - x0 * y;
					d3_geo_centroidX2 += z * (x0 + x);
					d3_geo_centroidY2 += z * (y0 + y);
					d3_geo_centroidZ2 += z * 3;
					d3_geo_pathCentroidPoint(x0 = x, y0 = y);
				}
				d3_geo_pathCentroid.lineEnd = function() {
					nextPoint(x00, y00);
				};
			}

			function d3_geo_pathContext(context) {
				var pointRadius = 4.5;
				var stream = {
					point: point,
					lineStart: function() {
						stream.point = pointLineStart;
					},
					lineEnd: lineEnd,
					polygonStart: function() {
						stream.lineEnd = lineEndPolygon;
					},
					polygonEnd: function() {
						stream.lineEnd = lineEnd;
						stream.point = point;
					},
					pointRadius: function(_) {
						pointRadius = _;
						return stream;
					},
					result: d3_noop
				};

				function point(x, y) {
					context.moveTo(x + pointRadius, y);
					context.arc(x, y, pointRadius, 0, τ);
				}

				function pointLineStart(x, y) {
					context.moveTo(x, y);
					stream.point = pointLine;
				}

				function pointLine(x, y) {
					context.lineTo(x, y);
				}

				function lineEnd() {
					stream.point = point;
				}

				function lineEndPolygon() {
					context.closePath();
				}
				return stream;
			}

			function d3_geo_resample(project) {
				var δ2 = .5,
					cosMinDistance = Math.cos(30 * d3_radians),
					maxDepth = 16;

				function resample(stream) {
					return(maxDepth ? resampleRecursive : resampleNone)(stream);
				}

				function resampleNone(stream) {
					return d3_geo_transformPoint(stream, function(x, y) {
						x = project(x, y);
						stream.point(x[0], x[1]);
					});
				}

				function resampleRecursive(stream) {
					var λ00, φ00, x00, y00, a00, b00, c00, λ0, x0, y0, a0, b0, c0;
					var resample = {
						point: point,
						lineStart: lineStart,
						lineEnd: lineEnd,
						polygonStart: function() {
							stream.polygonStart();
							resample.lineStart = ringStart;
						},
						polygonEnd: function() {
							stream.polygonEnd();
							resample.lineStart = lineStart;
						}
					};

					function point(x, y) {
						x = project(x, y);
						stream.point(x[0], x[1]);
					}

					function lineStart() {
						x0 = NaN;
						resample.point = linePoint;
						stream.lineStart();
					}

					function linePoint(λ, φ) {
						var c = d3_geo_cartesian([λ, φ]),
							p = project(λ, φ);
						resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream);
						stream.point(x0, y0);
					}

					function lineEnd() {
						resample.point = point;
						stream.lineEnd();
					}

					function ringStart() {
						lineStart();
						resample.point = ringPoint;
						resample.lineEnd = ringEnd;
					}

					function ringPoint(λ, φ) {
						linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
						resample.point = linePoint;
					}

					function ringEnd() {
						resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream);
						resample.lineEnd = lineEnd;
						lineEnd();
					}
					return resample;
				}

				function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) {
					var dx = x1 - x0,
						dy = y1 - y0,
						d2 = dx * dx + dy * dy;
					if(d2 > 4 * δ2 && depth--) {
						var a = a0 + a1,
							b = b0 + b1,
							c = c0 + c1,
							m = Math.sqrt(a * a + b * b + c * c),
							φ2 = Math.asin(c /= m),
							λ2 = abs(abs(c) - 1) < ε || abs(λ0 - λ1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a),
							p = project(λ2, φ2),
							x2 = p[0],
							y2 = p[1],
							dx2 = x2 - x0,
							dy2 = y2 - y0,
							dz = dy * dx2 - dx * dy2;
						if(dz * dz / d2 > δ2 || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) {
							resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream);
							stream.point(x2, y2);
							resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream);
						}
					}
				}
				resample.precision = function(_) {
					if(!arguments.length) return Math.sqrt(δ2);
					maxDepth = (δ2 = _ * _) > 0 && 16;
					return resample;
				};
				return resample;
			}
			d3.geo.path = function() {
				var pointRadius = 4.5,
					projection, context, projectStream, contextStream, cacheStream;

				function path(object) {
					if(object) {
						if(typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments));
						if(!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream);
						d3.geo.stream(object, cacheStream);
					}
					return contextStream.result();
				}
				path.area = function(object) {
					d3_geo_pathAreaSum = 0;
					d3.geo.stream(object, projectStream(d3_geo_pathArea));
					return d3_geo_pathAreaSum;
				};
				path.centroid = function(object) {
					d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
					d3.geo.stream(object, projectStream(d3_geo_pathCentroid));
					return d3_geo_centroidZ2 ? [d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2] : d3_geo_centroidZ1 ? [d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1] : d3_geo_centroidZ0 ? [d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0] : [NaN, NaN];
				};
				path.bounds = function(object) {
					d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity);
					d3.geo.stream(object, projectStream(d3_geo_pathBounds));
					return [
						[d3_geo_pathBoundsX0, d3_geo_pathBoundsY0],
						[d3_geo_pathBoundsX1, d3_geo_pathBoundsY1]
					];
				};
				path.projection = function(_) {
					if(!arguments.length) return projection;
					projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity;
					return reset();
				};
				path.context = function(_) {
					if(!arguments.length) return context;
					contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_);
					if(typeof pointRadius !== "function") contextStream.pointRadius(pointRadius);
					return reset();
				};
				path.pointRadius = function(_) {
					if(!arguments.length) return pointRadius;
					pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_);
					return path;
				};

				function reset() {
					cacheStream = null;
					return path;
				}
				return path.projection(d3.geo.albersUsa()).context(null);
			};

			function d3_geo_pathProjectStream(project) {
				var resample = d3_geo_resample(function(x, y) {
					return project([x * d3_degrees, y * d3_degrees]);
				});
				return function(stream) {
					return d3_geo_projectionRadians(resample(stream));
				};
			}
			d3.geo.transform = function(methods) {
				return {
					stream: function(stream) {
						var transform = new d3_geo_transform(stream);
						for(var k in methods) transform[k] = methods[k];
						return transform;
					}
				};
			};

			function d3_geo_transform(stream) {
				this.stream = stream;
			}
			d3_geo_transform.prototype = {
				point: function(x, y) {
					this.stream.point(x, y);
				},
				sphere: function() {
					this.stream.sphere();
				},
				lineStart: function() {
					this.stream.lineStart();
				},
				lineEnd: function() {
					this.stream.lineEnd();
				},
				polygonStart: function() {
					this.stream.polygonStart();
				},
				polygonEnd: function() {
					this.stream.polygonEnd();
				}
			};

			function d3_geo_transformPoint(stream, point) {
				return {
					point: point,
					sphere: function() {
						stream.sphere();
					},
					lineStart: function() {
						stream.lineStart();
					},
					lineEnd: function() {
						stream.lineEnd();
					},
					polygonStart: function() {
						stream.polygonStart();
					},
					polygonEnd: function() {
						stream.polygonEnd();
					}
				};
			}
			d3.geo.projection = d3_geo_projection;
			d3.geo.projectionMutator = d3_geo_projectionMutator;

			function d3_geo_projection(project) {
				return d3_geo_projectionMutator(function() {
					return project;
				})();
			}

			function d3_geo_projectionMutator(projectAt) {
				var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) {
						x = project(x, y);
						return [x[0] * k + δx, δy - x[1] * k];
					}),
					k = 150,
					x = 480,
					y = 250,
					λ = 0,
					φ = 0,
					δλ = 0,
					δφ = 0,
					δγ = 0,
					δx, δy, preclip = d3_geo_clipAntimeridian,
					postclip = d3_identity,
					clipAngle = null,
					clipExtent = null,
					stream;

				function projection(point) {
					point = projectRotate(point[0] * d3_radians, point[1] * d3_radians);
					return [point[0] * k + δx, δy - point[1] * k];
				}

				function invert(point) {
					point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k);
					return point && [point[0] * d3_degrees, point[1] * d3_degrees];
				}
				projection.stream = function(output) {
					if(stream) stream.valid = false;
					stream = d3_geo_projectionRadians(preclip(rotate, projectResample(postclip(output))));
					stream.valid = true;
					return stream;
				};
				projection.clipAngle = function(_) {
					if(!arguments.length) return clipAngle;
					preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians);
					return invalidate();
				};
				projection.clipExtent = function(_) {
					if(!arguments.length) return clipExtent;
					clipExtent = _;
					postclip = _ ? d3_geo_clipExtent(_[0][0], _[0][1], _[1][0], _[1][1]) : d3_identity;
					return invalidate();
				};
				projection.scale = function(_) {
					if(!arguments.length) return k;
					k = +_;
					return reset();
				};
				projection.translate = function(_) {
					if(!arguments.length) return [x, y];
					x = +_[0];
					y = +_[1];
					return reset();
				};
				projection.center = function(_) {
					if(!arguments.length) return [λ * d3_degrees, φ * d3_degrees];
					λ = _[0] % 360 * d3_radians;
					φ = _[1] % 360 * d3_radians;
					return reset();
				};
				projection.rotate = function(_) {
					if(!arguments.length) return [δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees];
					δλ = _[0] % 360 * d3_radians;
					δφ = _[1] % 360 * d3_radians;
					δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0;
					return reset();
				};
				d3.rebind(projection, projectResample, "precision");

				function reset() {
					projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project);
					var center = project(λ, φ);
					δx = x - center[0] * k;
					δy = y + center[1] * k;
					return invalidate();
				}

				function invalidate() {
					if(stream) stream.valid = false, stream = null;
					return projection;
				}
				return function() {
					project = projectAt.apply(this, arguments);
					projection.invert = project.invert && invert;
					return reset();
				};
			}

			function d3_geo_projectionRadians(stream) {
				return d3_geo_transformPoint(stream, function(x, y) {
					stream.point(x * d3_radians, y * d3_radians);
				});
			}

			function d3_geo_equirectangular(λ, φ) {
				return [λ, φ];
			}
			(d3.geo.equirectangular = function() {
				return d3_geo_projection(d3_geo_equirectangular);
			}).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular;
			d3.geo.rotation = function(rotate) {
				rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0);

				function forward(coordinates) {
					coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
					return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
				}
				forward.invert = function(coordinates) {
					coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
					return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
				};
				return forward;
			};

			function d3_geo_identityRotation(λ, φ) {
				return [λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ];
			}
			d3_geo_identityRotation.invert = d3_geo_equirectangular;

			function d3_geo_rotation(δλ, δφ, δγ) {
				return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation;
			}

			function d3_geo_forwardRotationλ(δλ) {
				return function(λ, φ) {
					return λ += δλ, [λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ];
				};
			}

			function d3_geo_rotationλ(δλ) {
				var rotation = d3_geo_forwardRotationλ(δλ);
				rotation.invert = d3_geo_forwardRotationλ(-δλ);
				return rotation;
			}

			function d3_geo_rotationφγ(δφ, δγ) {
				var cosδφ = Math.cos(δφ),
					sinδφ = Math.sin(δφ),
					cosδγ = Math.cos(δγ),
					sinδγ = Math.sin(δγ);

				function rotation(λ, φ) {
					var cosφ = Math.cos(φ),
						x = Math.cos(λ) * cosφ,
						y = Math.sin(λ) * cosφ,
						z = Math.sin(φ),
						k = z * cosδφ + x * sinδφ;
					return [Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), d3_asin(k * cosδγ + y * sinδγ)];
				}
				rotation.invert = function(λ, φ) {
					var cosφ = Math.cos(φ),
						x = Math.cos(λ) * cosφ,
						y = Math.sin(λ) * cosφ,
						z = Math.sin(φ),
						k = z * cosδγ - y * sinδγ;
					return [Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), d3_asin(k * cosδφ - x * sinδφ)];
				};
				return rotation;
			}
			d3.geo.circle = function() {
				var origin = [0, 0],
					angle, precision = 6,
					interpolate;

				function circle() {
					var center = typeof origin === "function" ? origin.apply(this, arguments) : origin,
						rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert,
						ring = [];
					interpolate(null, null, 1, {
						point: function(x, y) {
							ring.push(x = rotate(x, y));
							x[0] *= d3_degrees, x[1] *= d3_degrees;
						}
					});
					return {
						type: "Polygon",
						coordinates: [ring]
					};
				}
				circle.origin = function(x) {
					if(!arguments.length) return origin;
					origin = x;
					return circle;
				};
				circle.angle = function(x) {
					if(!arguments.length) return angle;
					interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians);
					return circle;
				};
				circle.precision = function(_) {
					if(!arguments.length) return precision;
					interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians);
					return circle;
				};
				return circle.angle(90);
			};

			function d3_geo_circleInterpolate(radius, precision) {
				var cr = Math.cos(radius),
					sr = Math.sin(radius);
				return function(from, to, direction, listener) {
					var step = direction * precision;
					if(from != null) {
						from = d3_geo_circleAngle(cr, from);
						to = d3_geo_circleAngle(cr, to);
						if(direction > 0 ? from < to : from > to) from += direction * τ;
					} else {
						from = radius + direction * τ;
						to = radius - .5 * step;
					}
					for(var point, t = from; direction > 0 ? t > to : t < to; t -= step) {
						listener.point((point = d3_geo_spherical([cr, -sr * Math.cos(t), -sr * Math.sin(t)]))[0], point[1]);
					}
				};
			}

			function d3_geo_circleAngle(cr, point) {
				var a = d3_geo_cartesian(point);
				a[0] -= cr;
				d3_geo_cartesianNormalize(a);
				var angle = d3_acos(-a[1]);
				return((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI);
			}
			d3.geo.distance = function(a, b) {
				var Δλ = (b[0] - a[0]) * d3_radians,
					φ0 = a[1] * d3_radians,
					φ1 = b[1] * d3_radians,
					sinΔλ = Math.sin(Δλ),
					cosΔλ = Math.cos(Δλ),
					sinφ0 = Math.sin(φ0),
					cosφ0 = Math.cos(φ0),
					sinφ1 = Math.sin(φ1),
					cosφ1 = Math.cos(φ1),
					t;
				return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ);
			};
			d3.geo.graticule = function() {
				var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10,
					dy = dx,
					DX = 90,
					DY = 360,
					x, y, X, Y, precision = 2.5;

				function graticule() {
					return {
						type: "MultiLineString",
						coordinates: lines()
					};
				}

				function lines() {
					return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) {
						return abs(x % DX) > ε;
					}).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) {
						return abs(y % DY) > ε;
					}).map(y));
				}
				graticule.lines = function() {
					return lines().map(function(coordinates) {
						return {
							type: "LineString",
							coordinates: coordinates
						};
					});
				};
				graticule.outline = function() {
					return {
						type: "Polygon",
						coordinates: [X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1))]
					};
				};
				graticule.extent = function(_) {
					if(!arguments.length) return graticule.minorExtent();
					return graticule.majorExtent(_).minorExtent(_);
				};
				graticule.majorExtent = function(_) {
					if(!arguments.length) return [
						[X0, Y0],
						[X1, Y1]
					];
					X0 = +_[0][0], X1 = +_[1][0];
					Y0 = +_[0][1], Y1 = +_[1][1];
					if(X0 > X1) _ = X0, X0 = X1, X1 = _;
					if(Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _;
					return graticule.precision(precision);
				};
				graticule.minorExtent = function(_) {
					if(!arguments.length) return [
						[x0, y0],
						[x1, y1]
					];
					x0 = +_[0][0], x1 = +_[1][0];
					y0 = +_[0][1], y1 = +_[1][1];
					if(x0 > x1) _ = x0, x0 = x1, x1 = _;
					if(y0 > y1) _ = y0, y0 = y1, y1 = _;
					return graticule.precision(precision);
				};
				graticule.step = function(_) {
					if(!arguments.length) return graticule.minorStep();
					return graticule.majorStep(_).minorStep(_);
				};
				graticule.majorStep = function(_) {
					if(!arguments.length) return [DX, DY];
					DX = +_[0], DY = +_[1];
					return graticule;
				};
				graticule.minorStep = function(_) {
					if(!arguments.length) return [dx, dy];
					dx = +_[0], dy = +_[1];
					return graticule;
				};
				graticule.precision = function(_) {
					if(!arguments.length) return precision;
					precision = +_;
					x = d3_geo_graticuleX(y0, y1, 90);
					y = d3_geo_graticuleY(x0, x1, precision);
					X = d3_geo_graticuleX(Y0, Y1, 90);
					Y = d3_geo_graticuleY(X0, X1, precision);
					return graticule;
				};
				return graticule.majorExtent([
					[-180, -90 + ε],
					[180, 90 - ε]
				]).minorExtent([
					[-180, -80 - ε],
					[180, 80 + ε]
				]);
			};

			function d3_geo_graticuleX(y0, y1, dy) {
				var y = d3.range(y0, y1 - ε, dy).concat(y1);
				return function(x) {
					return y.map(function(y) {
						return [x, y];
					});
				};
			}

			function d3_geo_graticuleY(x0, x1, dx) {
				var x = d3.range(x0, x1 - ε, dx).concat(x1);
				return function(y) {
					return x.map(function(x) {
						return [x, y];
					});
				};
			}

			function d3_source(d) {
				return d.source;
			}

			function d3_target(d) {
				return d.target;
			}
			d3.geo.greatArc = function() {
				var source = d3_source,
					source_, target = d3_target,
					target_;

				function greatArc() {
					return {
						type: "LineString",
						coordinates: [source_ || source.apply(this, arguments), target_ || target.apply(this, arguments)]
					};
				}
				greatArc.distance = function() {
					return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments));
				};
				greatArc.source = function(_) {
					if(!arguments.length) return source;
					source = _, source_ = typeof _ === "function" ? null : _;
					return greatArc;
				};
				greatArc.target = function(_) {
					if(!arguments.length) return target;
					target = _, target_ = typeof _ === "function" ? null : _;
					return greatArc;
				};
				greatArc.precision = function() {
					return arguments.length ? greatArc : 0;
				};
				return greatArc;
			};
			d3.geo.interpolate = function(source, target) {
				return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians);
			};

			function d3_geo_interpolate(x0, y0, x1, y1) {
				var cy0 = Math.cos(y0),
					sy0 = Math.sin(y0),
					cy1 = Math.cos(y1),
					sy1 = Math.sin(y1),
					kx0 = cy0 * Math.cos(x0),
					ky0 = cy0 * Math.sin(x0),
					kx1 = cy1 * Math.cos(x1),
					ky1 = cy1 * Math.sin(x1),
					d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))),
					k = 1 / Math.sin(d);
				var interpolate = d ? function(t) {
					var B = Math.sin(t *= d) * k,
						A = Math.sin(d - t) * k,
						x = A * kx0 + B * kx1,
						y = A * ky0 + B * ky1,
						z = A * sy0 + B * sy1;
					return [Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees];
				} : function() {
					return [x0 * d3_degrees, y0 * d3_degrees];
				};
				interpolate.distance = d;
				return interpolate;
			}
			d3.geo.length = function(object) {
				d3_geo_lengthSum = 0;
				d3.geo.stream(object, d3_geo_length);
				return d3_geo_lengthSum;
			};
			var d3_geo_lengthSum;
			var d3_geo_length = {
				sphere: d3_noop,
				point: d3_noop,
				lineStart: d3_geo_lengthLineStart,
				lineEnd: d3_noop,
				polygonStart: d3_noop,
				polygonEnd: d3_noop
			};

			function d3_geo_lengthLineStart() {
				var λ0, sinφ0, cosφ0;
				d3_geo_length.point = function(λ, φ) {
					λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ);
					d3_geo_length.point = nextPoint;
				};
				d3_geo_length.lineEnd = function() {
					d3_geo_length.point = d3_geo_length.lineEnd = d3_noop;
				};

				function nextPoint(λ, φ) {
					var sinφ = Math.sin(φ *= d3_radians),
						cosφ = Math.cos(φ),
						t = abs((λ *= d3_radians) - λ0),
						cosΔλ = Math.cos(t);
					d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ);
					λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ;
				}
			}

			function d3_geo_azimuthal(scale, angle) {
				function azimuthal(λ, φ) {
					var cosλ = Math.cos(λ),
						cosφ = Math.cos(φ),
						k = scale(cosλ * cosφ);
					return [k * cosφ * Math.sin(λ), k * Math.sin(φ)];
				}
				azimuthal.invert = function(x, y) {
					var ρ = Math.sqrt(x * x + y * y),
						c = angle(ρ),
						sinc = Math.sin(c),
						cosc = Math.cos(c);
					return [Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ)];
				};
				return azimuthal;
			}
			var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) {
				return Math.sqrt(2 / (1 + cosλcosφ));
			}, function(ρ) {
				return 2 * Math.asin(ρ / 2);
			});
			(d3.geo.azimuthalEqualArea = function() {
				return d3_geo_projection(d3_geo_azimuthalEqualArea);
			}).raw = d3_geo_azimuthalEqualArea;
			var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) {
				var c = Math.acos(cosλcosφ);
				return c && c / Math.sin(c);
			}, d3_identity);
			(d3.geo.azimuthalEquidistant = function() {
				return d3_geo_projection(d3_geo_azimuthalEquidistant);
			}).raw = d3_geo_azimuthalEquidistant;

			function d3_geo_conicConformal(φ0, φ1) {
				var cosφ0 = Math.cos(φ0),
					t = function(φ) {
						return Math.tan(π / 4 + φ / 2);
					},
					n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)),
					F = cosφ0 * Math.pow(t(φ0), n) / n;
				if(!n) return d3_geo_mercator;

				function forward(λ, φ) {
					if(F > 0) {
						if(φ < -halfπ + ε) φ = -halfπ + ε;
					} else {
						if(φ > halfπ - ε) φ = halfπ - ε;
					}
					var ρ = F / Math.pow(t(φ), n);
					return [ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ)];
				}
				forward.invert = function(x, y) {
					var ρ0_y = F - y,
						ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y);
					return [Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - halfπ];
				};
				return forward;
			}
			(d3.geo.conicConformal = function() {
				return d3_geo_conic(d3_geo_conicConformal);
			}).raw = d3_geo_conicConformal;

			function d3_geo_conicEquidistant(φ0, φ1) {
				var cosφ0 = Math.cos(φ0),
					n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0),
					G = cosφ0 / n + φ0;
				if(abs(n) < ε) return d3_geo_equirectangular;

				function forward(λ, φ) {
					var ρ = G - φ;
					return [ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ)];
				}
				forward.invert = function(x, y) {
					var ρ0_y = G - y;
					return [Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y)];
				};
				return forward;
			}
			(d3.geo.conicEquidistant = function() {
				return d3_geo_conic(d3_geo_conicEquidistant);
			}).raw = d3_geo_conicEquidistant;
			var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) {
				return 1 / cosλcosφ;
			}, Math.atan);
			(d3.geo.gnomonic = function() {
				return d3_geo_projection(d3_geo_gnomonic);
			}).raw = d3_geo_gnomonic;

			function d3_geo_mercator(λ, φ) {
				return [λ, Math.log(Math.tan(π / 4 + φ / 2))];
			}
			d3_geo_mercator.invert = function(x, y) {
				return [x, 2 * Math.atan(Math.exp(y)) - halfπ];
			};

			function d3_geo_mercatorProjection(project) {
				var m = d3_geo_projection(project),
					scale = m.scale,
					translate = m.translate,
					clipExtent = m.clipExtent,
					clipAuto;
				m.scale = function() {
					var v = scale.apply(m, arguments);
					return v === m ? clipAuto ? m.clipExtent(null) : m : v;
				};
				m.translate = function() {
					var v = translate.apply(m, arguments);
					return v === m ? clipAuto ? m.clipExtent(null) : m : v;
				};
				m.clipExtent = function(_) {
					var v = clipExtent.apply(m, arguments);
					if(v === m) {
						if(clipAuto = _ == null) {
							var k = π * scale(),
								t = translate();
							clipExtent([
								[t[0] - k, t[1] - k],
								[t[0] + k, t[1] + k]
							]);
						}
					} else if(clipAuto) {
						v = null;
					}
					return v;
				};
				return m.clipExtent(null);
			}
			(d3.geo.mercator = function() {
				return d3_geo_mercatorProjection(d3_geo_mercator);
			}).raw = d3_geo_mercator;
			var d3_geo_orthographic = d3_geo_azimuthal(function() {
				return 1;
			}, Math.asin);
			(d3.geo.orthographic = function() {
				return d3_geo_projection(d3_geo_orthographic);
			}).raw = d3_geo_orthographic;
			var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) {
				return 1 / (1 + cosλcosφ);
			}, function(ρ) {
				return 2 * Math.atan(ρ);
			});
			(d3.geo.stereographic = function() {
				return d3_geo_projection(d3_geo_stereographic);
			}).raw = d3_geo_stereographic;

			function d3_geo_transverseMercator(λ, φ) {
				return [Math.log(Math.tan(π / 4 + φ / 2)), -λ];
			}
			d3_geo_transverseMercator.invert = function(x, y) {
				return [-y, 2 * Math.atan(Math.exp(x)) - halfπ];
			};
			(d3.geo.transverseMercator = function() {
				var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator),
					center = projection.center,
					rotate = projection.rotate;
				projection.center = function(_) {
					return _ ? center([-_[1], _[0]]) : (_ = center(), [_[1], -_[0]]);
				};
				projection.rotate = function(_) {
					return _ ? rotate([_[0], _[1], _.length > 2 ? _[2] + 90 : 90]) : (_ = rotate(), [_[0], _[1], _[2] - 90]);
				};
				return rotate([0, 0, 90]);
			}).raw = d3_geo_transverseMercator;
			d3.geom = {};

			function d3_geom_pointX(d) {
				return d[0];
			}

			function d3_geom_pointY(d) {
				return d[1];
			}
			d3.geom.hull = function(vertices) {
				var x = d3_geom_pointX,
					y = d3_geom_pointY;
				if(arguments.length) return hull(vertices);

				function hull(data) {
					if(data.length < 3) return [];
					var fx = d3_functor(x),
						fy = d3_functor(y),
						i, n = data.length,
						points = [],
						flippedPoints = [];
					for(i = 0; i < n; i++) {
						points.push([+fx.call(this, data[i], i), +fy.call(this, data[i], i), i]);
					}
					points.sort(d3_geom_hullOrder);
					for(i = 0; i < n; i++) flippedPoints.push([points[i][0], -points[i][1]]);
					var upper = d3_geom_hullUpper(points),
						lower = d3_geom_hullUpper(flippedPoints);
					var skipLeft = lower[0] === upper[0],
						skipRight = lower[lower.length - 1] === upper[upper.length - 1],
						polygon = [];
					for(i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]);
					for(i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]);
					return polygon;
				}
				hull.x = function(_) {
					return arguments.length ? (x = _, hull) : x;
				};
				hull.y = function(_) {
					return arguments.length ? (y = _, hull) : y;
				};
				return hull;
			};

			function d3_geom_hullUpper(points) {
				var n = points.length,
					hull = [0, 1],
					hs = 2;
				for(var i = 2; i < n; i++) {
					while(hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs;
					hull[hs++] = i;
				}
				return hull.slice(0, hs);
			}

			function d3_geom_hullOrder(a, b) {
				return a[0] - b[0] || a[1] - b[1];
			}
			d3.geom.polygon = function(coordinates) {
				d3_subclass(coordinates, d3_geom_polygonPrototype);
				return coordinates;
			};
			var d3_geom_polygonPrototype = d3.geom.polygon.prototype = [];
			d3_geom_polygonPrototype.area = function() {
				var i = -1,
					n = this.length,
					a, b = this[n - 1],
					area = 0;
				while(++i < n) {
					a = b;
					b = this[i];
					area += a[1] * b[0] - a[0] * b[1];
				}
				return area * .5;
			};
			d3_geom_polygonPrototype.centroid = function(k) {
				var i = -1,
					n = this.length,
					x = 0,
					y = 0,
					a, b = this[n - 1],
					c;
				if(!arguments.length) k = -1 / (6 * this.area());
				while(++i < n) {
					a = b;
					b = this[i];
					c = a[0] * b[1] - b[0] * a[1];
					x += (a[0] + b[0]) * c;
					y += (a[1] + b[1]) * c;
				}
				return [x * k, y * k];
			};
			d3_geom_polygonPrototype.clip = function(subject) {
				var input, closed = d3_geom_polygonClosed(subject),
					i = -1,
					n = this.length - d3_geom_polygonClosed(this),
					j, m, a = this[n - 1],
					b, c, d;
				while(++i < n) {
					input = subject.slice();
					subject.length = 0;
					b = this[i];
					c = input[(m = input.length - closed) - 1];
					j = -1;
					while(++j < m) {
						d = input[j];
						if(d3_geom_polygonInside(d, a, b)) {
							if(!d3_geom_polygonInside(c, a, b)) {
								subject.push(d3_geom_polygonIntersect(c, d, a, b));
							}
							subject.push(d);
						} else if(d3_geom_polygonInside(c, a, b)) {
							subject.push(d3_geom_polygonIntersect(c, d, a, b));
						}
						c = d;
					}
					if(closed) subject.push(subject[0]);
					a = b;
				}
				return subject;
			};

			function d3_geom_polygonInside(p, a, b) {
				return(b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
			}

			function d3_geom_polygonIntersect(c, d, a, b) {
				var x1 = c[0],
					x3 = a[0],
					x21 = d[0] - x1,
					x43 = b[0] - x3,
					y1 = c[1],
					y3 = a[1],
					y21 = d[1] - y1,
					y43 = b[1] - y3,
					ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
				return [x1 + ua * x21, y1 + ua * y21];
			}

			function d3_geom_polygonClosed(coordinates) {
				var a = coordinates[0],
					b = coordinates[coordinates.length - 1];
				return !(a[0] - b[0] || a[1] - b[1]);
			}
			var d3_geom_voronoiEdges, d3_geom_voronoiCells, d3_geom_voronoiBeaches, d3_geom_voronoiBeachPool = [],
				d3_geom_voronoiFirstCircle, d3_geom_voronoiCircles, d3_geom_voronoiCirclePool = [];

			function d3_geom_voronoiBeach() {
				d3_geom_voronoiRedBlackNode(this);
				this.edge = this.site = this.circle = null;
			}

			function d3_geom_voronoiCreateBeach(site) {
				var beach = d3_geom_voronoiBeachPool.pop() || new d3_geom_voronoiBeach();
				beach.site = site;
				return beach;
			}

			function d3_geom_voronoiDetachBeach(beach) {
				d3_geom_voronoiDetachCircle(beach);
				d3_geom_voronoiBeaches.remove(beach);
				d3_geom_voronoiBeachPool.push(beach);
				d3_geom_voronoiRedBlackNode(beach);
			}

			function d3_geom_voronoiRemoveBeach(beach) {
				var circle = beach.circle,
					x = circle.x,
					y = circle.cy,
					vertex = {
						x: x,
						y: y
					},
					previous = beach.P,
					next = beach.N,
					disappearing = [beach];
				d3_geom_voronoiDetachBeach(beach);
				var lArc = previous;
				while(lArc.circle && abs(x - lArc.circle.x) < ε && abs(y - lArc.circle.cy) < ε) {
					previous = lArc.P;
					disappearing.unshift(lArc);
					d3_geom_voronoiDetachBeach(lArc);
					lArc = previous;
				}
				disappearing.unshift(lArc);
				d3_geom_voronoiDetachCircle(lArc);
				var rArc = next;
				while(rArc.circle && abs(x - rArc.circle.x) < ε && abs(y - rArc.circle.cy) < ε) {
					next = rArc.N;
					disappearing.push(rArc);
					d3_geom_voronoiDetachBeach(rArc);
					rArc = next;
				}
				disappearing.push(rArc);
				d3_geom_voronoiDetachCircle(rArc);
				var nArcs = disappearing.length,
					iArc;
				for(iArc = 1; iArc < nArcs; ++iArc) {
					rArc = disappearing[iArc];
					lArc = disappearing[iArc - 1];
					d3_geom_voronoiSetEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex);
				}
				lArc = disappearing[0];
				rArc = disappearing[nArcs - 1];
				rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, rArc.site, null, vertex);
				d3_geom_voronoiAttachCircle(lArc);
				d3_geom_voronoiAttachCircle(rArc);
			}

			function d3_geom_voronoiAddBeach(site) {
				var x = site.x,
					directrix = site.y,
					lArc, rArc, dxl, dxr, node = d3_geom_voronoiBeaches._;
				while(node) {
					dxl = d3_geom_voronoiLeftBreakPoint(node, directrix) - x;
					if(dxl > ε) node = node.L;
					else {
						dxr = x - d3_geom_voronoiRightBreakPoint(node, directrix);
						if(dxr > ε) {
							if(!node.R) {
								lArc = node;
								break;
							}
							node = node.R;
						} else {
							if(dxl > -ε) {
								lArc = node.P;
								rArc = node;
							} else if(dxr > -ε) {
								lArc = node;
								rArc = node.N;
							} else {
								lArc = rArc = node;
							}
							break;
						}
					}
				}
				var newArc = d3_geom_voronoiCreateBeach(site);
				d3_geom_voronoiBeaches.insert(lArc, newArc);
				if(!lArc && !rArc) return;
				if(lArc === rArc) {
					d3_geom_voronoiDetachCircle(lArc);
					rArc = d3_geom_voronoiCreateBeach(lArc.site);
					d3_geom_voronoiBeaches.insert(newArc, rArc);
					newArc.edge = rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
					d3_geom_voronoiAttachCircle(lArc);
					d3_geom_voronoiAttachCircle(rArc);
					return;
				}
				if(!rArc) {
					newArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
					return;
				}
				d3_geom_voronoiDetachCircle(lArc);
				d3_geom_voronoiDetachCircle(rArc);
				var lSite = lArc.site,
					ax = lSite.x,
					ay = lSite.y,
					bx = site.x - ax,
					by = site.y - ay,
					rSite = rArc.site,
					cx = rSite.x - ax,
					cy = rSite.y - ay,
					d = 2 * (bx * cy - by * cx),
					hb = bx * bx + by * by,
					hc = cx * cx + cy * cy,
					vertex = {
						x: (cy * hb - by * hc) / d + ax,
						y: (bx * hc - cx * hb) / d + ay
					};
				d3_geom_voronoiSetEdgeEnd(rArc.edge, lSite, rSite, vertex);
				newArc.edge = d3_geom_voronoiCreateEdge(lSite, site, null, vertex);
				rArc.edge = d3_geom_voronoiCreateEdge(site, rSite, null, vertex);
				d3_geom_voronoiAttachCircle(lArc);
				d3_geom_voronoiAttachCircle(rArc);
			}

			function d3_geom_voronoiLeftBreakPoint(arc, directrix) {
				var site = arc.site,
					rfocx = site.x,
					rfocy = site.y,
					pby2 = rfocy - directrix;
				if(!pby2) return rfocx;
				var lArc = arc.P;
				if(!lArc) return -Infinity;
				site = lArc.site;
				var lfocx = site.x,
					lfocy = site.y,
					plby2 = lfocy - directrix;
				if(!plby2) return lfocx;
				var hl = lfocx - rfocx,
					aby2 = 1 / pby2 - 1 / plby2,
					b = hl / plby2;
				if(aby2) return(-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx;
				return(rfocx + lfocx) / 2;
			}

			function d3_geom_voronoiRightBreakPoint(arc, directrix) {
				var rArc = arc.N;
				if(rArc) return d3_geom_voronoiLeftBreakPoint(rArc, directrix);
				var site = arc.site;
				return site.y === directrix ? site.x : Infinity;
			}

			function d3_geom_voronoiCell(site) {
				this.site = site;
				this.edges = [];
			}
			d3_geom_voronoiCell.prototype.prepare = function() {
				var halfEdges = this.edges,
					iHalfEdge = halfEdges.length,
					edge;
				while(iHalfEdge--) {
					edge = halfEdges[iHalfEdge].edge;
					if(!edge.b || !edge.a) halfEdges.splice(iHalfEdge, 1);
				}
				halfEdges.sort(d3_geom_voronoiHalfEdgeOrder);
				return halfEdges.length;
			};

			function d3_geom_voronoiCloseCells(extent) {
				var x0 = extent[0][0],
					x1 = extent[1][0],
					y0 = extent[0][1],
					y1 = extent[1][1],
					x2, y2, x3, y3, cells = d3_geom_voronoiCells,
					iCell = cells.length,
					cell, iHalfEdge, halfEdges, nHalfEdges, start, end;
				while(iCell--) {
					cell = cells[iCell];
					if(!cell || !cell.prepare()) continue;
					halfEdges = cell.edges;
					nHalfEdges = halfEdges.length;
					iHalfEdge = 0;
					while(iHalfEdge < nHalfEdges) {
						end = halfEdges[iHalfEdge].end(), x3 = end.x, y3 = end.y;
						start = halfEdges[++iHalfEdge % nHalfEdges].start(), x2 = start.x, y2 = start.y;
						if(abs(x3 - x2) > ε || abs(y3 - y2) > ε) {
							halfEdges.splice(iHalfEdge, 0, new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site, end, abs(x3 - x0) < ε && y1 - y3 > ε ? {
								x: x0,
								y: abs(x2 - x0) < ε ? y2 : y1
							} : abs(y3 - y1) < ε && x1 - x3 > ε ? {
								x: abs(y2 - y1) < ε ? x2 : x1,
								y: y1
							} : abs(x3 - x1) < ε && y3 - y0 > ε ? {
								x: x1,
								y: abs(x2 - x1) < ε ? y2 : y0
							} : abs(y3 - y0) < ε && x3 - x0 > ε ? {
								x: abs(y2 - y0) < ε ? x2 : x0,
								y: y0
							} : null), cell.site, null));
							++nHalfEdges;
						}
					}
				}
			}

			function d3_geom_voronoiHalfEdgeOrder(a, b) {
				return b.angle - a.angle;
			}

			function d3_geom_voronoiCircle() {
				d3_geom_voronoiRedBlackNode(this);
				this.x = this.y = this.arc = this.site = this.cy = null;
			}

			function d3_geom_voronoiAttachCircle(arc) {
				var lArc = arc.P,
					rArc = arc.N;
				if(!lArc || !rArc) return;
				var lSite = lArc.site,
					cSite = arc.site,
					rSite = rArc.site;
				if(lSite === rSite) return;
				var bx = cSite.x,
					by = cSite.y,
					ax = lSite.x - bx,
					ay = lSite.y - by,
					cx = rSite.x - bx,
					cy = rSite.y - by;
				var d = 2 * (ax * cy - ay * cx);
				if(d >= -ε2) return;
				var ha = ax * ax + ay * ay,
					hc = cx * cx + cy * cy,
					x = (cy * ha - ay * hc) / d,
					y = (ax * hc - cx * ha) / d,
					cy = y + by;
				var circle = d3_geom_voronoiCirclePool.pop() || new d3_geom_voronoiCircle();
				circle.arc = arc;
				circle.site = cSite;
				circle.x = x + bx;
				circle.y = cy + Math.sqrt(x * x + y * y);
				circle.cy = cy;
				arc.circle = circle;
				var before = null,
					node = d3_geom_voronoiCircles._;
				while(node) {
					if(circle.y < node.y || circle.y === node.y && circle.x <= node.x) {
						if(node.L) node = node.L;
						else {
							before = node.P;
							break;
						}
					} else {
						if(node.R) node = node.R;
						else {
							before = node;
							break;
						}
					}
				}
				d3_geom_voronoiCircles.insert(before, circle);
				if(!before) d3_geom_voronoiFirstCircle = circle;
			}

			function d3_geom_voronoiDetachCircle(arc) {
				var circle = arc.circle;
				if(circle) {
					if(!circle.P) d3_geom_voronoiFirstCircle = circle.N;
					d3_geom_voronoiCircles.remove(circle);
					d3_geom_voronoiCirclePool.push(circle);
					d3_geom_voronoiRedBlackNode(circle);
					arc.circle = null;
				}
			}

			function d3_geom_voronoiClipEdges(extent) {
				var edges = d3_geom_voronoiEdges,
					clip = d3_geom_clipLine(extent[0][0], extent[0][1], extent[1][0], extent[1][1]),
					i = edges.length,
					e;
				while(i--) {
					e = edges[i];
					if(!d3_geom_voronoiConnectEdge(e, extent) || !clip(e) || abs(e.a.x - e.b.x) < ε && abs(e.a.y - e.b.y) < ε) {
						e.a = e.b = null;
						edges.splice(i, 1);
					}
				}
			}

			function d3_geom_voronoiConnectEdge(edge, extent) {
				var vb = edge.b;
				if(vb) return true;
				var va = edge.a,
					x0 = extent[0][0],
					x1 = extent[1][0],
					y0 = extent[0][1],
					y1 = extent[1][1],
					lSite = edge.l,
					rSite = edge.r,
					lx = lSite.x,
					ly = lSite.y,
					rx = rSite.x,
					ry = rSite.y,
					fx = (lx + rx) / 2,
					fy = (ly + ry) / 2,
					fm, fb;
				if(ry === ly) {
					if(fx < x0 || fx >= x1) return;
					if(lx > rx) {
						if(!va) va = {
							x: fx,
							y: y0
						};
						else if(va.y >= y1) return;
						vb = {
							x: fx,
							y: y1
						};
					} else {
						if(!va) va = {
							x: fx,
							y: y1
						};
						else if(va.y < y0) return;
						vb = {
							x: fx,
							y: y0
						};
					}
				} else {
					fm = (lx - rx) / (ry - ly);
					fb = fy - fm * fx;
					if(fm < -1 || fm > 1) {
						if(lx > rx) {
							if(!va) va = {
								x: (y0 - fb) / fm,
								y: y0
							};
							else if(va.y >= y1) return;
							vb = {
								x: (y1 - fb) / fm,
								y: y1
							};
						} else {
							if(!va) va = {
								x: (y1 - fb) / fm,
								y: y1
							};
							else if(va.y < y0) return;
							vb = {
								x: (y0 - fb) / fm,
								y: y0
							};
						}
					} else {
						if(ly < ry) {
							if(!va) va = {
								x: x0,
								y: fm * x0 + fb
							};
							else if(va.x >= x1) return;
							vb = {
								x: x1,
								y: fm * x1 + fb
							};
						} else {
							if(!va) va = {
								x: x1,
								y: fm * x1 + fb
							};
							else if(va.x < x0) return;
							vb = {
								x: x0,
								y: fm * x0 + fb
							};
						}
					}
				}
				edge.a = va;
				edge.b = vb;
				return true;
			}

			function d3_geom_voronoiEdge(lSite, rSite) {
				this.l = lSite;
				this.r = rSite;
				this.a = this.b = null;
			}

			function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) {
				var edge = new d3_geom_voronoiEdge(lSite, rSite);
				d3_geom_voronoiEdges.push(edge);
				if(va) d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, va);
				if(vb) d3_geom_voronoiSetEdgeEnd(edge, rSite, lSite, vb);
				d3_geom_voronoiCells[lSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, lSite, rSite));
				d3_geom_voronoiCells[rSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, rSite, lSite));
				return edge;
			}

			function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) {
				var edge = new d3_geom_voronoiEdge(lSite, null);
				edge.a = va;
				edge.b = vb;
				d3_geom_voronoiEdges.push(edge);
				return edge;
			}

			function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) {
				if(!edge.a && !edge.b) {
					edge.a = vertex;
					edge.l = lSite;
					edge.r = rSite;
				} else if(edge.l === rSite) {
					edge.b = vertex;
				} else {
					edge.a = vertex;
				}
			}

			function d3_geom_voronoiHalfEdge(edge, lSite, rSite) {
				var va = edge.a,
					vb = edge.b;
				this.edge = edge;
				this.site = lSite;
				this.angle = rSite ? Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x) : edge.l === lSite ? Math.atan2(vb.x - va.x, va.y - vb.y) : Math.atan2(va.x - vb.x, vb.y - va.y);
			}
			d3_geom_voronoiHalfEdge.prototype = {
				start: function() {
					return this.edge.l === this.site ? this.edge.a : this.edge.b;
				},
				end: function() {
					return this.edge.l === this.site ? this.edge.b : this.edge.a;
				}
			};

			function d3_geom_voronoiRedBlackTree() {
				this._ = null;
			}

			function d3_geom_voronoiRedBlackNode(node) {
				node.U = node.C = node.L = node.R = node.P = node.N = null;
			}
			d3_geom_voronoiRedBlackTree.prototype = {
				insert: function(after, node) {
					var parent, grandpa, uncle;
					if(after) {
						node.P = after;
						node.N = after.N;
						if(after.N) after.N.P = node;
						after.N = node;
						if(after.R) {
							after = after.R;
							while(after.L) after = after.L;
							after.L = node;
						} else {
							after.R = node;
						}
						parent = after;
					} else if(this._) {
						after = d3_geom_voronoiRedBlackFirst(this._);
						node.P = null;
						node.N = after;
						after.P = after.L = node;
						parent = after;
					} else {
						node.P = node.N = null;
						this._ = node;
						parent = null;
					}
					node.L = node.R = null;
					node.U = parent;
					node.C = true;
					after = node;
					while(parent && parent.C) {
						grandpa = parent.U;
						if(parent === grandpa.L) {
							uncle = grandpa.R;
							if(uncle && uncle.C) {
								parent.C = uncle.C = false;
								grandpa.C = true;
								after = grandpa;
							} else {
								if(after === parent.R) {
									d3_geom_voronoiRedBlackRotateLeft(this, parent);
									after = parent;
									parent = after.U;
								}
								parent.C = false;
								grandpa.C = true;
								d3_geom_voronoiRedBlackRotateRight(this, grandpa);
							}
						} else {
							uncle = grandpa.L;
							if(uncle && uncle.C) {
								parent.C = uncle.C = false;
								grandpa.C = true;
								after = grandpa;
							} else {
								if(after === parent.L) {
									d3_geom_voronoiRedBlackRotateRight(this, parent);
									after = parent;
									parent = after.U;
								}
								parent.C = false;
								grandpa.C = true;
								d3_geom_voronoiRedBlackRotateLeft(this, grandpa);
							}
						}
						parent = after.U;
					}
					this._.C = false;
				},
				remove: function(node) {
					if(node.N) node.N.P = node.P;
					if(node.P) node.P.N = node.N;
					node.N = node.P = null;
					var parent = node.U,
						sibling, left = node.L,
						right = node.R,
						next, red;
					if(!left) next = right;
					else if(!right) next = left;
					else next = d3_geom_voronoiRedBlackFirst(right);
					if(parent) {
						if(parent.L === node) parent.L = next;
						else parent.R = next;
					} else {
						this._ = next;
					}
					if(left && right) {
						red = next.C;
						next.C = node.C;
						next.L = left;
						left.U = next;
						if(next !== right) {
							parent = next.U;
							next.U = node.U;
							node = next.R;
							parent.L = node;
							next.R = right;
							right.U = next;
						} else {
							next.U = parent;
							parent = next;
							node = next.R;
						}
					} else {
						red = node.C;
						node = next;
					}
					if(node) node.U = parent;
					if(red) return;
					if(node && node.C) {
						node.C = false;
						return;
					}
					do {
						if(node === this._) break;
						if(node === parent.L) {
							sibling = parent.R;
							if(sibling.C) {
								sibling.C = false;
								parent.C = true;
								d3_geom_voronoiRedBlackRotateLeft(this, parent);
								sibling = parent.R;
							}
							if(sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
								if(!sibling.R || !sibling.R.C) {
									sibling.L.C = false;
									sibling.C = true;
									d3_geom_voronoiRedBlackRotateRight(this, sibling);
									sibling = parent.R;
								}
								sibling.C = parent.C;
								parent.C = sibling.R.C = false;
								d3_geom_voronoiRedBlackRotateLeft(this, parent);
								node = this._;
								break;
							}
						} else {
							sibling = parent.L;
							if(sibling.C) {
								sibling.C = false;
								parent.C = true;
								d3_geom_voronoiRedBlackRotateRight(this, parent);
								sibling = parent.L;
							}
							if(sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
								if(!sibling.L || !sibling.L.C) {
									sibling.R.C = false;
									sibling.C = true;
									d3_geom_voronoiRedBlackRotateLeft(this, sibling);
									sibling = parent.L;
								}
								sibling.C = parent.C;
								parent.C = sibling.L.C = false;
								d3_geom_voronoiRedBlackRotateRight(this, parent);
								node = this._;
								break;
							}
						}
						sibling.C = true;
						node = parent;
						parent = parent.U;
					} while (!node.C);
					if(node) node.C = false;
				}
			};

			function d3_geom_voronoiRedBlackRotateLeft(tree, node) {
				var p = node,
					q = node.R,
					parent = p.U;
				if(parent) {
					if(parent.L === p) parent.L = q;
					else parent.R = q;
				} else {
					tree._ = q;
				}
				q.U = parent;
				p.U = q;
				p.R = q.L;
				if(p.R) p.R.U = p;
				q.L = p;
			}

			function d3_geom_voronoiRedBlackRotateRight(tree, node) {
				var p = node,
					q = node.L,
					parent = p.U;
				if(parent) {
					if(parent.L === p) parent.L = q;
					else parent.R = q;
				} else {
					tree._ = q;
				}
				q.U = parent;
				p.U = q;
				p.L = q.R;
				if(p.L) p.L.U = p;
				q.R = p;
			}

			function d3_geom_voronoiRedBlackFirst(node) {
				while(node.L) node = node.L;
				return node;
			}

			function d3_geom_voronoi(sites, bbox) {
				var site = sites.sort(d3_geom_voronoiVertexOrder).pop(),
					x0, y0, circle;
				d3_geom_voronoiEdges = [];
				d3_geom_voronoiCells = new Array(sites.length);
				d3_geom_voronoiBeaches = new d3_geom_voronoiRedBlackTree();
				d3_geom_voronoiCircles = new d3_geom_voronoiRedBlackTree();
				while(true) {
					circle = d3_geom_voronoiFirstCircle;
					if(site && (!circle || site.y < circle.y || site.y === circle.y && site.x < circle.x)) {
						if(site.x !== x0 || site.y !== y0) {
							d3_geom_voronoiCells[site.i] = new d3_geom_voronoiCell(site);
							d3_geom_voronoiAddBeach(site);
							x0 = site.x, y0 = site.y;
						}
						site = sites.pop();
					} else if(circle) {
						d3_geom_voronoiRemoveBeach(circle.arc);
					} else {
						break;
					}
				}
				if(bbox) d3_geom_voronoiClipEdges(bbox), d3_geom_voronoiCloseCells(bbox);
				var diagram = {
					cells: d3_geom_voronoiCells,
					edges: d3_geom_voronoiEdges
				};
				d3_geom_voronoiBeaches = d3_geom_voronoiCircles = d3_geom_voronoiEdges = d3_geom_voronoiCells = null;
				return diagram;
			}

			function d3_geom_voronoiVertexOrder(a, b) {
				return b.y - a.y || b.x - a.x;
			}
			d3.geom.voronoi = function(points) {
				var x = d3_geom_pointX,
					y = d3_geom_pointY,
					fx = x,
					fy = y,
					clipExtent = d3_geom_voronoiClipExtent;
				if(points) return voronoi(points);

				function voronoi(data) {
					var polygons = new Array(data.length),
						x0 = clipExtent[0][0],
						y0 = clipExtent[0][1],
						x1 = clipExtent[1][0],
						y1 = clipExtent[1][1];
					d3_geom_voronoi(sites(data), clipExtent).cells.forEach(function(cell, i) {
						var edges = cell.edges,
							site = cell.site,
							polygon = polygons[i] = edges.length ? edges.map(function(e) {
								var s = e.start();
								return [s.x, s.y];
							}) : site.x >= x0 && site.x <= x1 && site.y >= y0 && site.y <= y1 ? [
								[x0, y1],
								[x1, y1],
								[x1, y0],
								[x0, y0]
							] : [];
						polygon.point = data[i];
					});
					return polygons;
				}

				function sites(data) {
					return data.map(function(d, i) {
						return {
							x: Math.round(fx(d, i) / ε) * ε,
							y: Math.round(fy(d, i) / ε) * ε,
							i: i
						};
					});
				}
				voronoi.links = function(data) {
					return d3_geom_voronoi(sites(data)).edges.filter(function(edge) {
						return edge.l && edge.r;
					}).map(function(edge) {
						return {
							source: data[edge.l.i],
							target: data[edge.r.i]
						};
					});
				};
				voronoi.triangles = function(data) {
					var triangles = [];
					d3_geom_voronoi(sites(data)).cells.forEach(function(cell, i) {
						var site = cell.site,
							edges = cell.edges.sort(d3_geom_voronoiHalfEdgeOrder),
							j = -1,
							m = edges.length,
							e0, s0, e1 = edges[m - 1].edge,
							s1 = e1.l === site ? e1.r : e1.l;
						while(++j < m) {
							e0 = e1;
							s0 = s1;
							e1 = edges[j].edge;
							s1 = e1.l === site ? e1.r : e1.l;
							if(i < s0.i && i < s1.i && d3_geom_voronoiTriangleArea(site, s0, s1) < 0) {
								triangles.push([data[i], data[s0.i], data[s1.i]]);
							}
						}
					});
					return triangles;
				};
				voronoi.x = function(_) {
					return arguments.length ? (fx = d3_functor(x = _), voronoi) : x;
				};
				voronoi.y = function(_) {
					return arguments.length ? (fy = d3_functor(y = _), voronoi) : y;
				};
				voronoi.clipExtent = function(_) {
					if(!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent;
					clipExtent = _ == null ? d3_geom_voronoiClipExtent : _;
					return voronoi;
				};
				voronoi.size = function(_) {
					if(!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent && clipExtent[1];
					return voronoi.clipExtent(_ && [
						[0, 0], _
					]);
				};
				return voronoi;
			};
			var d3_geom_voronoiClipExtent = [
				[-1e6, -1e6],
				[1e6, 1e6]
			];

			function d3_geom_voronoiTriangleArea(a, b, c) {
				return(a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y);
			}
			d3.geom.delaunay = function(vertices) {
				return d3.geom.voronoi().triangles(vertices);
			};
			d3.geom.quadtree = function(points, x1, y1, x2, y2) {
				var x = d3_geom_pointX,
					y = d3_geom_pointY,
					compat;
				if(compat = arguments.length) {
					x = d3_geom_quadtreeCompatX;
					y = d3_geom_quadtreeCompatY;
					if(compat === 3) {
						y2 = y1;
						x2 = x1;
						y1 = x1 = 0;
					}
					return quadtree(points);
				}

				function quadtree(data) {
					var d, fx = d3_functor(x),
						fy = d3_functor(y),
						xs, ys, i, n, x1_, y1_, x2_, y2_;
					if(x1 != null) {
						x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2;
					} else {
						x2_ = y2_ = -(x1_ = y1_ = Infinity);
						xs = [], ys = [];
						n = data.length;
						if(compat)
							for(i = 0; i < n; ++i) {
								d = data[i];
								if(d.x < x1_) x1_ = d.x;
								if(d.y < y1_) y1_ = d.y;
								if(d.x > x2_) x2_ = d.x;
								if(d.y > y2_) y2_ = d.y;
								xs.push(d.x);
								ys.push(d.y);
							} else
								for(i = 0; i < n; ++i) {
									var x_ = +fx(d = data[i], i),
										y_ = +fy(d, i);
									if(x_ < x1_) x1_ = x_;
									if(y_ < y1_) y1_ = y_;
									if(x_ > x2_) x2_ = x_;
									if(y_ > y2_) y2_ = y_;
									xs.push(x_);
									ys.push(y_);
								}
					}
					var dx = x2_ - x1_,
						dy = y2_ - y1_;
					if(dx > dy) y2_ = y1_ + dx;
					else x2_ = x1_ + dy;

					function insert(n, d, x, y, x1, y1, x2, y2) {
						if(isNaN(x) || isNaN(y)) return;
						if(n.leaf) {
							var nx = n.x,
								ny = n.y;
							if(nx != null) {
								if(abs(nx - x) + abs(ny - y) < .01) {
									insertChild(n, d, x, y, x1, y1, x2, y2);
								} else {
									var nPoint = n.point;
									n.x = n.y = n.point = null;
									insertChild(n, nPoint, nx, ny, x1, y1, x2, y2);
									insertChild(n, d, x, y, x1, y1, x2, y2);
								}
							} else {
								n.x = x, n.y = y, n.point = d;
							}
						} else {
							insertChild(n, d, x, y, x1, y1, x2, y2);
						}
					}

					function insertChild(n, d, x, y, x1, y1, x2, y2) {
						var xm = (x1 + x2) * .5,
							ym = (y1 + y2) * .5,
							right = x >= xm,
							below = y >= ym,
							i = below << 1 | right;
						n.leaf = false;
						n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode());
						if(right) x1 = xm;
						else x2 = xm;
						if(below) y1 = ym;
						else y2 = ym;
						insert(n, d, x, y, x1, y1, x2, y2);
					}
					var root = d3_geom_quadtreeNode();
					root.add = function(d) {
						insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_);
					};
					root.visit = function(f) {
						d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_);
					};
					root.find = function(point) {
						return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_);
					};
					i = -1;
					if(x1 == null) {
						while(++i < n) {
							insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_);
						}
						--i;
					} else data.forEach(root.add);
					xs = ys = data = d = null;
					return root;
				}
				quadtree.x = function(_) {
					return arguments.length ? (x = _, quadtree) : x;
				};
				quadtree.y = function(_) {
					return arguments.length ? (y = _, quadtree) : y;
				};
				quadtree.extent = function(_) {
					if(!arguments.length) return x1 == null ? null : [
						[x1, y1],
						[x2, y2]
					];
					if(_ == null) x1 = y1 = x2 = y2 = null;
					else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0],
						y2 = +_[1][1];
					return quadtree;
				};
				quadtree.size = function(_) {
					if(!arguments.length) return x1 == null ? null : [x2 - x1, y2 - y1];
					if(_ == null) x1 = y1 = x2 = y2 = null;
					else x1 = y1 = 0, x2 = +_[0], y2 = +_[1];
					return quadtree;
				};
				return quadtree;
			};

			function d3_geom_quadtreeCompatX(d) {
				return d.x;
			}

			function d3_geom_quadtreeCompatY(d) {
				return d.y;
			}

			function d3_geom_quadtreeNode() {
				return {
					leaf: true,
					nodes: [],
					point: null,
					x: null,
					y: null
				};
			}

			function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) {
				if(!f(node, x1, y1, x2, y2)) {
					var sx = (x1 + x2) * .5,
						sy = (y1 + y2) * .5,
						children = node.nodes;
					if(children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy);
					if(children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy);
					if(children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2);
					if(children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2);
				}
			}

			function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) {
				var minDistance2 = Infinity,
					closestPoint;
				(function find(node, x1, y1, x2, y2) {
					if(x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return;
					if(point = node.point) {
						var point, dx = x - node.x,
							dy = y - node.y,
							distance2 = dx * dx + dy * dy;
						if(distance2 < minDistance2) {
							var distance = Math.sqrt(minDistance2 = distance2);
							x0 = x - distance, y0 = y - distance;
							x3 = x + distance, y3 = y + distance;
							closestPoint = point;
						}
					}
					var children = node.nodes,
						xm = (x1 + x2) * .5,
						ym = (y1 + y2) * .5,
						right = x >= xm,
						below = y >= ym;
					for(var i = below << 1 | right, j = i + 4; i < j; ++i) {
						if(node = children[i & 3]) switch(i & 3) {
							case 0:
								find(node, x1, y1, xm, ym);
								break;

							case 1:
								find(node, xm, y1, x2, ym);
								break;

							case 2:
								find(node, x1, ym, xm, y2);
								break;

							case 3:
								find(node, xm, ym, x2, y2);
								break;
						}
					}
				})(root, x0, y0, x3, y3);
				return closestPoint;
			}
			d3.interpolateRgb = d3_interpolateRgb;

			function d3_interpolateRgb(a, b) {
				a = d3.rgb(a);
				b = d3.rgb(b);
				var ar = a.r,
					ag = a.g,
					ab = a.b,
					br = b.r - ar,
					bg = b.g - ag,
					bb = b.b - ab;
				return function(t) {
					return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t));
				};
			}
			d3.interpolateObject = d3_interpolateObject;

			function d3_interpolateObject(a, b) {
				var i = {},
					c = {},
					k;
				for(k in a) {
					if(k in b) {
						i[k] = d3_interpolate(a[k], b[k]);
					} else {
						c[k] = a[k];
					}
				}
				for(k in b) {
					if(!(k in a)) {
						c[k] = b[k];
					}
				}
				return function(t) {
					for(k in i) c[k] = i[k](t);
					return c;
				};
			}
			d3.interpolateNumber = d3_interpolateNumber;

			function d3_interpolateNumber(a, b) {
				a = +a, b = +b;
				return function(t) {
					return a * (1 - t) + b * t;
				};
			}
			d3.interpolateString = d3_interpolateString;

			function d3_interpolateString(a, b) {
				var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0,
					am, bm, bs, i = -1,
					s = [],
					q = [];
				a = a + "", b = b + "";
				while((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) {
					if((bs = bm.index) > bi) {
						bs = b.slice(bi, bs);
						if(s[i]) s[i] += bs;
						else s[++i] = bs;
					}
					if((am = am[0]) === (bm = bm[0])) {
						if(s[i]) s[i] += bm;
						else s[++i] = bm;
					} else {
						s[++i] = null;
						q.push({
							i: i,
							x: d3_interpolateNumber(am, bm)
						});
					}
					bi = d3_interpolate_numberB.lastIndex;
				}
				if(bi < b.length) {
					bs = b.slice(bi);
					if(s[i]) s[i] += bs;
					else s[++i] = bs;
				}
				return s.length < 2 ? q[0] ? (b = q[0].x, function(t) {
					return b(t) + "";
				}) : function() {
					return b;
				} : (b = q.length, function(t) {
					for(var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t);
					return s.join("");
				});
			}
			var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g,
				d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g");
			d3.interpolate = d3_interpolate;

			function d3_interpolate(a, b) {
				var i = d3.interpolators.length,
					f;
				while(--i >= 0 && !(f = d3.interpolators[i](a, b)));
				return f;
			}
			d3.interpolators = [function(a, b) {
				var t = typeof b;
				return(t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb\(|hsl\()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b);
			}];
			d3.interpolateArray = d3_interpolateArray;

			function d3_interpolateArray(a, b) {
				var x = [],
					c = [],
					na = a.length,
					nb = b.length,
					n0 = Math.min(a.length, b.length),
					i;
				for(i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i]));
				for(; i < na; ++i) c[i] = a[i];
				for(; i < nb; ++i) c[i] = b[i];
				return function(t) {
					for(i = 0; i < n0; ++i) c[i] = x[i](t);
					return c;
				};
			}
			var d3_ease_default = function() {
				return d3_identity;
			};
			var d3_ease = d3.map({
				linear: d3_ease_default,
				poly: d3_ease_poly,
				quad: function() {
					return d3_ease_quad;
				},
				cubic: function() {
					return d3_ease_cubic;
				},
				sin: function() {
					return d3_ease_sin;
				},
				exp: function() {
					return d3_ease_exp;
				},
				circle: function() {
					return d3_ease_circle;
				},
				elastic: d3_ease_elastic,
				back: d3_ease_back,
				bounce: function() {
					return d3_ease_bounce;
				}
			});
			var d3_ease_mode = d3.map({
				"in": d3_identity,
				out: d3_ease_reverse,
				"in-out": d3_ease_reflect,
				"out-in": function(f) {
					return d3_ease_reflect(d3_ease_reverse(f));
				}
			});
			d3.ease = function(name) {
				var i = name.indexOf("-"),
					t = i >= 0 ? name.slice(0, i) : name,
					m = i >= 0 ? name.slice(i + 1) : "in";
				t = d3_ease.get(t) || d3_ease_default;
				m = d3_ease_mode.get(m) || d3_identity;
				return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1))));
			};

			function d3_ease_clamp(f) {
				return function(t) {
					return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
				};
			}

			function d3_ease_reverse(f) {
				return function(t) {
					return 1 - f(1 - t);
				};
			}

			function d3_ease_reflect(f) {
				return function(t) {
					return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t));
				};
			}

			function d3_ease_quad(t) {
				return t * t;
			}

			function d3_ease_cubic(t) {
				return t * t * t;
			}

			function d3_ease_cubicInOut(t) {
				if(t <= 0) return 0;
				if(t >= 1) return 1;
				var t2 = t * t,
					t3 = t2 * t;
				return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75);
			}

			function d3_ease_poly(e) {
				return function(t) {
					return Math.pow(t, e);
				};
			}

			function d3_ease_sin(t) {
				return 1 - Math.cos(t * halfπ);
			}

			function d3_ease_exp(t) {
				return Math.pow(2, 10 * (t - 1));
			}

			function d3_ease_circle(t) {
				return 1 - Math.sqrt(1 - t * t);
			}

			function d3_ease_elastic(a, p) {
				var s;
				if(arguments.length < 2) p = .45;
				if(arguments.length) s = p / τ * Math.asin(1 / a);
				else a = 1, s = p / 4;
				return function(t) {
					return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * τ / p);
				};
			}

			function d3_ease_back(s) {
				if(!s) s = 1.70158;
				return function(t) {
					return t * t * ((s + 1) * t - s);
				};
			}

			function d3_ease_bounce(t) {
				return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375;
			}
			d3.interpolateHcl = d3_interpolateHcl;

			function d3_interpolateHcl(a, b) {
				a = d3.hcl(a);
				b = d3.hcl(b);
				var ah = a.h,
					ac = a.c,
					al = a.l,
					bh = b.h - ah,
					bc = b.c - ac,
					bl = b.l - al;
				if(isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac;
				if(isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah;
				else if(bh > 180) bh -= 360;
				else if(bh < -180) bh += 360;
				return function(t) {
					return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + "";
				};
			}
			d3.interpolateHsl = d3_interpolateHsl;

			function d3_interpolateHsl(a, b) {
				a = d3.hsl(a);
				b = d3.hsl(b);
				var ah = a.h,
					as = a.s,
					al = a.l,
					bh = b.h - ah,
					bs = b.s - as,
					bl = b.l - al;
				if(isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as;
				if(isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah;
				else if(bh > 180) bh -= 360;
				else if(bh < -180) bh += 360;
				return function(t) {
					return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + "";
				};
			}
			d3.interpolateLab = d3_interpolateLab;

			function d3_interpolateLab(a, b) {
				a = d3.lab(a);
				b = d3.lab(b);
				var al = a.l,
					aa = a.a,
					ab = a.b,
					bl = b.l - al,
					ba = b.a - aa,
					bb = b.b - ab;
				return function(t) {
					return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + "";
				};
			}
			d3.interpolateRound = d3_interpolateRound;

			function d3_interpolateRound(a, b) {
				b -= a;
				return function(t) {
					return Math.round(a + b * t);
				};
			}
			d3.transform = function(string) {
				var g = d3_document.createElementNS(d3.ns.prefix.svg, "g");
				return(d3.transform = function(string) {
					if(string != null) {
						g.setAttribute("transform", string);
						var t = g.transform.baseVal.consolidate();
					}
					return new d3_transform(t ? t.matrix : d3_transformIdentity);
				})(string);
			};

			function d3_transform(m) {
				var r0 = [m.a, m.b],
					r1 = [m.c, m.d],
					kx = d3_transformNormalize(r0),
					kz = d3_transformDot(r0, r1),
					ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0;
				if(r0[0] * r1[1] < r1[0] * r0[1]) {
					r0[0] *= -1;
					r0[1] *= -1;
					kx *= -1;
					kz *= -1;
				}
				this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees;
				this.translate = [m.e, m.f];
				this.scale = [kx, ky];
				this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0;
			}
			d3_transform.prototype.toString = function() {
				return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")";
			};

			function d3_transformDot(a, b) {
				return a[0] * b[0] + a[1] * b[1];
			}

			function d3_transformNormalize(a) {
				var k = Math.sqrt(d3_transformDot(a, a));
				if(k) {
					a[0] /= k;
					a[1] /= k;
				}
				return k;
			}

			function d3_transformCombine(a, b, k) {
				a[0] += k * b[0];
				a[1] += k * b[1];
				return a;
			}
			var d3_transformIdentity = {
				a: 1,
				b: 0,
				c: 0,
				d: 1,
				e: 0,
				f: 0
			};
			d3.interpolateTransform = d3_interpolateTransform;

			function d3_interpolateTransform(a, b) {
				var s = [],
					q = [],
					n, A = d3.transform(a),
					B = d3.transform(b),
					ta = A.translate,
					tb = B.translate,
					ra = A.rotate,
					rb = B.rotate,
					wa = A.skew,
					wb = B.skew,
					ka = A.scale,
					kb = B.scale;
				if(ta[0] != tb[0] || ta[1] != tb[1]) {
					s.push("translate(", null, ",", null, ")");
					q.push({
						i: 1,
						x: d3_interpolateNumber(ta[0], tb[0])
					}, {
						i: 3,
						x: d3_interpolateNumber(ta[1], tb[1])
					});
				} else if(tb[0] || tb[1]) {
					s.push("translate(" + tb + ")");
				} else {
					s.push("");
				}
				if(ra != rb) {
					if(ra - rb > 180) rb += 360;
					else if(rb - ra > 180) ra += 360;
					q.push({
						i: s.push(s.pop() + "rotate(", null, ")") - 2,
						x: d3_interpolateNumber(ra, rb)
					});
				} else if(rb) {
					s.push(s.pop() + "rotate(" + rb + ")");
				}
				if(wa != wb) {
					q.push({
						i: s.push(s.pop() + "skewX(", null, ")") - 2,
						x: d3_interpolateNumber(wa, wb)
					});
				} else if(wb) {
					s.push(s.pop() + "skewX(" + wb + ")");
				}
				if(ka[0] != kb[0] || ka[1] != kb[1]) {
					n = s.push(s.pop() + "scale(", null, ",", null, ")");
					q.push({
						i: n - 4,
						x: d3_interpolateNumber(ka[0], kb[0])
					}, {
						i: n - 2,
						x: d3_interpolateNumber(ka[1], kb[1])
					});
				} else if(kb[0] != 1 || kb[1] != 1) {
					s.push(s.pop() + "scale(" + kb + ")");
				}
				n = q.length;
				return function(t) {
					var i = -1,
						o;
					while(++i < n) s[(o = q[i]).i] = o.x(t);
					return s.join("");
				};
			}

			function d3_uninterpolateNumber(a, b) {
				b = (b -= a = +a) || 1 / b;
				return function(x) {
					return(x - a) / b;
				};
			}

			function d3_uninterpolateClamp(a, b) {
				b = (b -= a = +a) || 1 / b;
				return function(x) {
					return Math.max(0, Math.min(1, (x - a) / b));
				};
			}
			d3.layout = {};
			d3.layout.bundle = function() {
				return function(links) {
					var paths = [],
						i = -1,
						n = links.length;
					while(++i < n) paths.push(d3_layout_bundlePath(links[i]));
					return paths;
				};
			};

			function d3_layout_bundlePath(link) {
				var start = link.source,
					end = link.target,
					lca = d3_layout_bundleLeastCommonAncestor(start, end),
					points = [start];
				while(start !== lca) {
					start = start.parent;
					points.push(start);
				}
				var k = points.length;
				while(end !== lca) {
					points.splice(k, 0, end);
					end = end.parent;
				}
				return points;
			}

			function d3_layout_bundleAncestors(node) {
				var ancestors = [],
					parent = node.parent;
				while(parent != null) {
					ancestors.push(node);
					node = parent;
					parent = parent.parent;
				}
				ancestors.push(node);
				return ancestors;
			}

			function d3_layout_bundleLeastCommonAncestor(a, b) {
				if(a === b) return a;
				var aNodes = d3_layout_bundleAncestors(a),
					bNodes = d3_layout_bundleAncestors(b),
					aNode = aNodes.pop(),
					bNode = bNodes.pop(),
					sharedNode = null;
				while(aNode === bNode) {
					sharedNode = aNode;
					aNode = aNodes.pop();
					bNode = bNodes.pop();
				}
				return sharedNode;
			}
			d3.layout.chord = function() {
				var chord = {},
					chords, groups, matrix, n, padding = 0,
					sortGroups, sortSubgroups, sortChords;

				function relayout() {
					var subgroups = {},
						groupSums = [],
						groupIndex = d3.range(n),
						subgroupIndex = [],
						k, x, x0, i, j;
					chords = [];
					groups = [];
					k = 0, i = -1;
					while(++i < n) {
						x = 0, j = -1;
						while(++j < n) {
							x += matrix[i][j];
						}
						groupSums.push(x);
						subgroupIndex.push(d3.range(n));
						k += x;
					}
					if(sortGroups) {
						groupIndex.sort(function(a, b) {
							return sortGroups(groupSums[a], groupSums[b]);
						});
					}
					if(sortSubgroups) {
						subgroupIndex.forEach(function(d, i) {
							d.sort(function(a, b) {
								return sortSubgroups(matrix[i][a], matrix[i][b]);
							});
						});
					}
					k = (τ - padding * n) / k;
					x = 0, i = -1;
					while(++i < n) {
						x0 = x, j = -1;
						while(++j < n) {
							var di = groupIndex[i],
								dj = subgroupIndex[di][j],
								v = matrix[di][dj],
								a0 = x,
								a1 = x += v * k;
							subgroups[di + "-" + dj] = {
								index: di,
								subindex: dj,
								startAngle: a0,
								endAngle: a1,
								value: v
							};
						}
						groups[di] = {
							index: di,
							startAngle: x0,
							endAngle: x,
							value: (x - x0) / k
						};
						x += padding;
					}
					i = -1;
					while(++i < n) {
						j = i - 1;
						while(++j < n) {
							var source = subgroups[i + "-" + j],
								target = subgroups[j + "-" + i];
							if(source.value || target.value) {
								chords.push(source.value < target.value ? {
									source: target,
									target: source
								} : {
									source: source,
									target: target
								});
							}
						}
					}
					if(sortChords) resort();
				}

				function resort() {
					chords.sort(function(a, b) {
						return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
					});
				}
				chord.matrix = function(x) {
					if(!arguments.length) return matrix;
					n = (matrix = x) && matrix.length;
					chords = groups = null;
					return chord;
				};
				chord.padding = function(x) {
					if(!arguments.length) return padding;
					padding = x;
					chords = groups = null;
					return chord;
				};
				chord.sortGroups = function(x) {
					if(!arguments.length) return sortGroups;
					sortGroups = x;
					chords = groups = null;
					return chord;
				};
				chord.sortSubgroups = function(x) {
					if(!arguments.length) return sortSubgroups;
					sortSubgroups = x;
					chords = null;
					return chord;
				};
				chord.sortChords = function(x) {
					if(!arguments.length) return sortChords;
					sortChords = x;
					if(chords) resort();
					return chord;
				};
				chord.chords = function() {
					if(!chords) relayout();
					return chords;
				};
				chord.groups = function() {
					if(!groups) relayout();
					return groups;
				};
				return chord;
			};
			d3.layout.force = function() {
				var force = {},
					event = d3.dispatch("start", "tick", "end"),
					size = [1, 1],
					drag, alpha, friction = .9,
					linkDistance = d3_layout_forceLinkDistance,
					linkStrength = d3_layout_forceLinkStrength,
					charge = -30,
					chargeDistance2 = d3_layout_forceChargeDistance2,
					gravity = .1,
					theta2 = .64,
					nodes = [],
					links = [],
					distances, strengths, charges;

				function repulse(node) {
					return function(quad, x1, _, x2) {
						if(quad.point !== node) {
							var dx = quad.cx - node.x,
								dy = quad.cy - node.y,
								dw = x2 - x1,
								dn = dx * dx + dy * dy;
							if(dw * dw / theta2 < dn) {
								if(dn < chargeDistance2) {
									var k = quad.charge / dn;
									node.px -= dx * k;
									node.py -= dy * k;
								}
								return true;
							}
							if(quad.point && dn && dn < chargeDistance2) {
								var k = quad.pointCharge / dn;
								node.px -= dx * k;
								node.py -= dy * k;
							}
						}
						return !quad.charge;
					};
				}
				force.tick = function() {
					if((alpha *= .99) < .005) {
						event.end({
							type: "end",
							alpha: alpha = 0
						});
						return true;
					}
					var n = nodes.length,
						m = links.length,
						q, i, o, s, t, l, k, x, y;
					for(i = 0; i < m; ++i) {
						o = links[i];
						s = o.source;
						t = o.target;
						x = t.x - s.x;
						y = t.y - s.y;
						if(l = x * x + y * y) {
							l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
							x *= l;
							y *= l;
							t.x -= x * (k = s.weight / (t.weight + s.weight));
							t.y -= y * k;
							s.x += x * (k = 1 - k);
							s.y += y * k;
						}
					}
					if(k = alpha * gravity) {
						x = size[0] / 2;
						y = size[1] / 2;
						i = -1;
						if(k)
							while(++i < n) {
								o = nodes[i];
								o.x += (x - o.x) * k;
								o.y += (y - o.y) * k;
							}
					}
					if(charge) {
						d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges);
						i = -1;
						while(++i < n) {
							if(!(o = nodes[i]).fixed) {
								q.visit(repulse(o));
							}
						}
					}
					i = -1;
					while(++i < n) {
						o = nodes[i];
						if(o.fixed) {
							o.x = o.px;
							o.y = o.py;
						} else {
							o.x -= (o.px - (o.px = o.x)) * friction;
							o.y -= (o.py - (o.py = o.y)) * friction;
						}
					}
					event.tick({
						type: "tick",
						alpha: alpha
					});
				};
				force.nodes = function(x) {
					if(!arguments.length) return nodes;
					nodes = x;
					return force;
				};
				force.links = function(x) {
					if(!arguments.length) return links;
					links = x;
					return force;
				};
				force.size = function(x) {
					if(!arguments.length) return size;
					size = x;
					return force;
				};
				force.linkDistance = function(x) {
					if(!arguments.length) return linkDistance;
					linkDistance = typeof x === "function" ? x : +x;
					return force;
				};
				force.distance = force.linkDistance;
				force.linkStrength = function(x) {
					if(!arguments.length) return linkStrength;
					linkStrength = typeof x === "function" ? x : +x;
					return force;
				};
				force.friction = function(x) {
					if(!arguments.length) return friction;
					friction = +x;
					return force;
				};
				force.charge = function(x) {
					if(!arguments.length) return charge;
					charge = typeof x === "function" ? x : +x;
					return force;
				};
				force.chargeDistance = function(x) {
					if(!arguments.length) return Math.sqrt(chargeDistance2);
					chargeDistance2 = x * x;
					return force;
				};
				force.gravity = function(x) {
					if(!arguments.length) return gravity;
					gravity = +x;
					return force;
				};
				force.theta = function(x) {
					if(!arguments.length) return Math.sqrt(theta2);
					theta2 = x * x;
					return force;
				};
				force.alpha = function(x) {
					if(!arguments.length) return alpha;
					x = +x;
					if(alpha) {
						if(x > 0) alpha = x;
						else alpha = 0;
					} else if(x > 0) {
						event.start({
							type: "start",
							alpha: alpha = x
						});
						d3.timer(force.tick);
					}
					return force;
				};
				force.start = function() {
					var i, n = nodes.length,
						m = links.length,
						w = size[0],
						h = size[1],
						neighbors, o;
					for(i = 0; i < n; ++i) {
						(o = nodes[i]).index = i;
						o.weight = 0;
					}
					for(i = 0; i < m; ++i) {
						o = links[i];
						if(typeof o.source == "number") o.source = nodes[o.source];
						if(typeof o.target == "number") o.target = nodes[o.target];
						++o.source.weight;
						++o.target.weight;
					}
					for(i = 0; i < n; ++i) {
						o = nodes[i];
						if(isNaN(o.x)) o.x = position("x", w);
						if(isNaN(o.y)) o.y = position("y", h);
						if(isNaN(o.px)) o.px = o.x;
						if(isNaN(o.py)) o.py = o.y;
					}
					distances = [];
					if(typeof linkDistance === "function")
						for(i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i);
					else
						for(i = 0; i < m; ++i) distances[i] = linkDistance;
					strengths = [];
					if(typeof linkStrength === "function")
						for(i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i);
					else
						for(i = 0; i < m; ++i) strengths[i] = linkStrength;
					charges = [];
					if(typeof charge === "function")
						for(i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i);
					else
						for(i = 0; i < n; ++i) charges[i] = charge;

					function position(dimension, size) {
						if(!neighbors) {
							neighbors = new Array(n);
							for(j = 0; j < n; ++j) {
								neighbors[j] = [];
							}
							for(j = 0; j < m; ++j) {
								var o = links[j];
								neighbors[o.source.index].push(o.target);
								neighbors[o.target.index].push(o.source);
							}
						}
						var candidates = neighbors[i],
							j = -1,
							l = candidates.length,
							x;
						while(++j < l)
							if(!isNaN(x = candidates[j][dimension])) return x;
						return Math.random() * size;
					}
					return force.resume();
				};
				force.resume = function() {
					return force.alpha(.1);
				};
				force.stop = function() {
					return force.alpha(0);
				};
				force.drag = function() {
					if(!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend);
					if(!arguments.length) return drag;
					this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag);
				};

				function dragmove(d) {
					d.px = d3.event.x, d.py = d3.event.y;
					force.resume();
				}
				return d3.rebind(force, event, "on");
			};

			function d3_layout_forceDragstart(d) {
				d.fixed |= 2;
			}

			function d3_layout_forceDragend(d) {
				d.fixed &= ~6;
			}

			function d3_layout_forceMouseover(d) {
				d.fixed |= 4;
				d.px = d.x, d.py = d.y;
			}

			function d3_layout_forceMouseout(d) {
				d.fixed &= ~4;
			}

			function d3_layout_forceAccumulate(quad, alpha, charges) {
				var cx = 0,
					cy = 0;
				quad.charge = 0;
				if(!quad.leaf) {
					var nodes = quad.nodes,
						n = nodes.length,
						i = -1,
						c;
					while(++i < n) {
						c = nodes[i];
						if(c == null) continue;
						d3_layout_forceAccumulate(c, alpha, charges);
						quad.charge += c.charge;
						cx += c.charge * c.cx;
						cy += c.charge * c.cy;
					}
				}
				if(quad.point) {
					if(!quad.leaf) {
						quad.point.x += Math.random() - .5;
						quad.point.y += Math.random() - .5;
					}
					var k = alpha * charges[quad.point.index];
					quad.charge += quad.pointCharge = k;
					cx += k * quad.point.x;
					cy += k * quad.point.y;
				}
				quad.cx = cx / quad.charge;
				quad.cy = cy / quad.charge;
			}
			var d3_layout_forceLinkDistance = 20,
				d3_layout_forceLinkStrength = 1,
				d3_layout_forceChargeDistance2 = Infinity;
			d3.layout.hierarchy = function() {
				var sort = d3_layout_hierarchySort,
					children = d3_layout_hierarchyChildren,
					value = d3_layout_hierarchyValue;

				function hierarchy(root) {
					var stack = [root],
						nodes = [],
						node;
					root.depth = 0;
					while((node = stack.pop()) != null) {
						nodes.push(node);
						if((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) {
							var n, childs, child;
							while(--n >= 0) {
								stack.push(child = childs[n]);
								child.parent = node;
								child.depth = node.depth + 1;
							}
							if(value) node.value = 0;
							node.children = childs;
						} else {
							if(value) node.value = +value.call(hierarchy, node, node.depth) || 0;
							delete node.children;
						}
					}
					d3_layout_hierarchyVisitAfter(root, function(node) {
						var childs, parent;
						if(sort && (childs = node.children)) childs.sort(sort);
						if(value && (parent = node.parent)) parent.value += node.value;
					});
					return nodes;
				}
				hierarchy.sort = function(x) {
					if(!arguments.length) return sort;
					sort = x;
					return hierarchy;
				};
				hierarchy.children = function(x) {
					if(!arguments.length) return children;
					children = x;
					return hierarchy;
				};
				hierarchy.value = function(x) {
					if(!arguments.length) return value;
					value = x;
					return hierarchy;
				};
				hierarchy.revalue = function(root) {
					if(value) {
						d3_layout_hierarchyVisitBefore(root, function(node) {
							if(node.children) node.value = 0;
						});
						d3_layout_hierarchyVisitAfter(root, function(node) {
							var parent;
							if(!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0;
							if(parent = node.parent) parent.value += node.value;
						});
					}
					return root;
				};
				return hierarchy;
			};

			function d3_layout_hierarchyRebind(object, hierarchy) {
				d3.rebind(object, hierarchy, "sort", "children", "value");
				object.nodes = object;
				object.links = d3_layout_hierarchyLinks;
				return object;
			}

			function d3_layout_hierarchyVisitBefore(node, callback) {
				var nodes = [node];
				while((node = nodes.pop()) != null) {
					callback(node);
					if((children = node.children) && (n = children.length)) {
						var n, children;
						while(--n >= 0) nodes.push(children[n]);
					}
				}
			}

			function d3_layout_hierarchyVisitAfter(node, callback) {
				var nodes = [node],
					nodes2 = [];
				while((node = nodes.pop()) != null) {
					nodes2.push(node);
					if((children = node.children) && (n = children.length)) {
						var i = -1,
							n, children;
						while(++i < n) nodes.push(children[i]);
					}
				}
				while((node = nodes2.pop()) != null) {
					callback(node);
				}
			}

			function d3_layout_hierarchyChildren(d) {
				return d.children;
			}

			function d3_layout_hierarchyValue(d) {
				return d.value;
			}

			function d3_layout_hierarchySort(a, b) {
				return b.value - a.value;
			}

			function d3_layout_hierarchyLinks(nodes) {
				return d3.merge(nodes.map(function(parent) {
					return(parent.children || []).map(function(child) {
						return {
							source: parent,
							target: child
						};
					});
				}));
			}
			d3.layout.partition = function() {
				var hierarchy = d3.layout.hierarchy(),
					size = [1, 1];

				function position(node, x, dx, dy) {
					var children = node.children;
					node.x = x;
					node.y = node.depth * dy;
					node.dx = dx;
					node.dy = dy;
					if(children && (n = children.length)) {
						var i = -1,
							n, c, d;
						dx = node.value ? dx / node.value : 0;
						while(++i < n) {
							position(c = children[i], x, d = c.value * dx, dy);
							x += d;
						}
					}
				}

				function depth(node) {
					var children = node.children,
						d = 0;
					if(children && (n = children.length)) {
						var i = -1,
							n;
						while(++i < n) d = Math.max(d, depth(children[i]));
					}
					return 1 + d;
				}

				function partition(d, i) {
					var nodes = hierarchy.call(this, d, i);
					position(nodes[0], 0, size[0], size[1] / depth(nodes[0]));
					return nodes;
				}
				partition.size = function(x) {
					if(!arguments.length) return size;
					size = x;
					return partition;
				};
				return d3_layout_hierarchyRebind(partition, hierarchy);
			};
			d3.layout.pie = function() {
				var value = Number,
					sort = d3_layout_pieSortByValue,
					startAngle = 0,
					endAngle = τ,
					padAngle = 0;

				function pie(data) {
					var n = data.length,
						values = data.map(function(d, i) {
							return +value.call(pie, d, i);
						}),
						a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle),
						da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a,
						p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)),
						pa = p * (da < 0 ? -1 : 1),
						k = (da - n * pa) / d3.sum(values),
						index = d3.range(n),
						arcs = [],
						v;
					if(sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) {
						return values[j] - values[i];
					} : function(i, j) {
						return sort(data[i], data[j]);
					});
					index.forEach(function(i) {
						arcs[i] = {
							data: data[i],
							value: v = values[i],
							startAngle: a,
							endAngle: a += v * k + pa,
							padAngle: p
						};
					});
					return arcs;
				}
				pie.value = function(_) {
					if(!arguments.length) return value;
					value = _;
					return pie;
				};
				pie.sort = function(_) {
					if(!arguments.length) return sort;
					sort = _;
					return pie;
				};
				pie.startAngle = function(_) {
					if(!arguments.length) return startAngle;
					startAngle = _;
					return pie;
				};
				pie.endAngle = function(_) {
					if(!arguments.length) return endAngle;
					endAngle = _;
					return pie;
				};
				pie.padAngle = function(_) {
					if(!arguments.length) return padAngle;
					padAngle = _;
					return pie;
				};
				return pie;
			};
			var d3_layout_pieSortByValue = {};
			d3.layout.stack = function() {
				var values = d3_identity,
					order = d3_layout_stackOrderDefault,
					offset = d3_layout_stackOffsetZero,
					out = d3_layout_stackOut,
					x = d3_layout_stackX,
					y = d3_layout_stackY;

				function stack(data, index) {
					if(!(n = data.length)) return data;
					var series = data.map(function(d, i) {
						return values.call(stack, d, i);
					});
					var points = series.map(function(d) {
						return d.map(function(v, i) {
							return [x.call(stack, v, i), y.call(stack, v, i)];
						});
					});
					var orders = order.call(stack, points, index);
					series = d3.permute(series, orders);
					points = d3.permute(points, orders);
					var offsets = offset.call(stack, points, index);
					var m = series[0].length,
						n, i, j, o;
					for(j = 0; j < m; ++j) {
						out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
						for(i = 1; i < n; ++i) {
							out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
						}
					}
					return data;
				}
				stack.values = function(x) {
					if(!arguments.length) return values;
					values = x;
					return stack;
				};
				stack.order = function(x) {
					if(!arguments.length) return order;
					order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault;
					return stack;
				};
				stack.offset = function(x) {
					if(!arguments.length) return offset;
					offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero;
					return stack;
				};
				stack.x = function(z) {
					if(!arguments.length) return x;
					x = z;
					return stack;
				};
				stack.y = function(z) {
					if(!arguments.length) return y;
					y = z;
					return stack;
				};
				stack.out = function(z) {
					if(!arguments.length) return out;
					out = z;
					return stack;
				};
				return stack;
			};

			function d3_layout_stackX(d) {
				return d.x;
			}

			function d3_layout_stackY(d) {
				return d.y;
			}

			function d3_layout_stackOut(d, y0, y) {
				d.y0 = y0;
				d.y = y;
			}
			var d3_layout_stackOrders = d3.map({
				"inside-out": function(data) {
					var n = data.length,
						i, j, max = data.map(d3_layout_stackMaxIndex),
						sums = data.map(d3_layout_stackReduceSum),
						index = d3.range(n).sort(function(a, b) {
							return max[a] - max[b];
						}),
						top = 0,
						bottom = 0,
						tops = [],
						bottoms = [];
					for(i = 0; i < n; ++i) {
						j = index[i];
						if(top < bottom) {
							top += sums[j];
							tops.push(j);
						} else {
							bottom += sums[j];
							bottoms.push(j);
						}
					}
					return bottoms.reverse().concat(tops);
				},
				reverse: function(data) {
					return d3.range(data.length).reverse();
				},
				"default": d3_layout_stackOrderDefault
			});
			var d3_layout_stackOffsets = d3.map({
				silhouette: function(data) {
					var n = data.length,
						m = data[0].length,
						sums = [],
						max = 0,
						i, j, o, y0 = [];
					for(j = 0; j < m; ++j) {
						for(i = 0, o = 0; i < n; i++) o += data[i][j][1];
						if(o > max) max = o;
						sums.push(o);
					}
					for(j = 0; j < m; ++j) {
						y0[j] = (max - sums[j]) / 2;
					}
					return y0;
				},
				wiggle: function(data) {
					var n = data.length,
						x = data[0],
						m = x.length,
						i, j, k, s1, s2, s3, dx, o, o0, y0 = [];
					y0[0] = o = o0 = 0;
					for(j = 1; j < m; ++j) {
						for(i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1];
						for(i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) {
							for(k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) {
								s3 += (data[k][j][1] - data[k][j - 1][1]) / dx;
							}
							s2 += s3 * data[i][j][1];
						}
						y0[j] = o -= s1 ? s2 / s1 * dx : 0;
						if(o < o0) o0 = o;
					}
					for(j = 0; j < m; ++j) y0[j] -= o0;
					return y0;
				},
				expand: function(data) {
					var n = data.length,
						m = data[0].length,
						k = 1 / n,
						i, j, o, y0 = [];
					for(j = 0; j < m; ++j) {
						for(i = 0, o = 0; i < n; i++) o += data[i][j][1];
						if(o)
							for(i = 0; i < n; i++) data[i][j][1] /= o;
						else
							for(i = 0; i < n; i++) data[i][j][1] = k;
					}
					for(j = 0; j < m; ++j) y0[j] = 0;
					return y0;
				},
				zero: d3_layout_stackOffsetZero
			});

			function d3_layout_stackOrderDefault(data) {
				return d3.range(data.length);
			}

			function d3_layout_stackOffsetZero(data) {
				var j = -1,
					m = data[0].length,
					y0 = [];
				while(++j < m) y0[j] = 0;
				return y0;
			}

			function d3_layout_stackMaxIndex(array) {
				var i = 1,
					j = 0,
					v = array[0][1],
					k, n = array.length;
				for(; i < n; ++i) {
					if((k = array[i][1]) > v) {
						j = i;
						v = k;
					}
				}
				return j;
			}

			function d3_layout_stackReduceSum(d) {
				return d.reduce(d3_layout_stackSum, 0);
			}

			function d3_layout_stackSum(p, d) {
				return p + d[1];
			}
			d3.layout.histogram = function() {
				var frequency = true,
					valuer = Number,
					ranger = d3_layout_histogramRange,
					binner = d3_layout_histogramBinSturges;

				function histogram(data, i) {
					var bins = [],
						values = data.map(valuer, this),
						range = ranger.call(this, values, i),
						thresholds = binner.call(this, range, values, i),
						bin, i = -1,
						n = values.length,
						m = thresholds.length - 1,
						k = frequency ? 1 : 1 / n,
						x;
					while(++i < m) {
						bin = bins[i] = [];
						bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]);
						bin.y = 0;
					}
					if(m > 0) {
						i = -1;
						while(++i < n) {
							x = values[i];
							if(x >= range[0] && x <= range[1]) {
								bin = bins[d3.bisect(thresholds, x, 1, m) - 1];
								bin.y += k;
								bin.push(data[i]);
							}
						}
					}
					return bins;
				}
				histogram.value = function(x) {
					if(!arguments.length) return valuer;
					valuer = x;
					return histogram;
				};
				histogram.range = function(x) {
					if(!arguments.length) return ranger;
					ranger = d3_functor(x);
					return histogram;
				};
				histogram.bins = function(x) {
					if(!arguments.length) return binner;
					binner = typeof x === "number" ? function(range) {
						return d3_layout_histogramBinFixed(range, x);
					} : d3_functor(x);
					return histogram;
				};
				histogram.frequency = function(x) {
					if(!arguments.length) return frequency;
					frequency = !!x;
					return histogram;
				};
				return histogram;
			};

			function d3_layout_histogramBinSturges(range, values) {
				return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1));
			}

			function d3_layout_histogramBinFixed(range, n) {
				var x = -1,
					b = +range[0],
					m = (range[1] - b) / n,
					f = [];
				while(++x <= n) f[x] = m * x + b;
				return f;
			}

			function d3_layout_histogramRange(values) {
				return [d3.min(values), d3.max(values)];
			}
			d3.layout.pack = function() {
				var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort),
					padding = 0,
					size = [1, 1],
					radius;

				function pack(d, i) {
					var nodes = hierarchy.call(this, d, i),
						root = nodes[0],
						w = size[0],
						h = size[1],
						r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() {
							return radius;
						};
					root.x = root.y = 0;
					d3_layout_hierarchyVisitAfter(root, function(d) {
						d.r = +r(d.value);
					});
					d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
					if(padding) {
						var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2;
						d3_layout_hierarchyVisitAfter(root, function(d) {
							d.r += dr;
						});
						d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
						d3_layout_hierarchyVisitAfter(root, function(d) {
							d.r -= dr;
						});
					}
					d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h));
					return nodes;
				}
				pack.size = function(_) {
					if(!arguments.length) return size;
					size = _;
					return pack;
				};
				pack.radius = function(_) {
					if(!arguments.length) return radius;
					radius = _ == null || typeof _ === "function" ? _ : +_;
					return pack;
				};
				pack.padding = function(_) {
					if(!arguments.length) return padding;
					padding = +_;
					return pack;
				};
				return d3_layout_hierarchyRebind(pack, hierarchy);
			};

			function d3_layout_packSort(a, b) {
				return a.value - b.value;
			}

			function d3_layout_packInsert(a, b) {
				var c = a._pack_next;
				a._pack_next = b;
				b._pack_prev = a;
				b._pack_next = c;
				c._pack_prev = b;
			}

			function d3_layout_packSplice(a, b) {
				a._pack_next = b;
				b._pack_prev = a;
			}

			function d3_layout_packIntersects(a, b) {
				var dx = b.x - a.x,
					dy = b.y - a.y,
					dr = a.r + b.r;
				return .999 * dr * dr > dx * dx + dy * dy;
			}

			function d3_layout_packSiblings(node) {
				if(!(nodes = node.children) || !(n = nodes.length)) return;
				var nodes, xMin = Infinity,
					xMax = -Infinity,
					yMin = Infinity,
					yMax = -Infinity,
					a, b, c, i, j, k, n;

				function bound(node) {
					xMin = Math.min(node.x - node.r, xMin);
					xMax = Math.max(node.x + node.r, xMax);
					yMin = Math.min(node.y - node.r, yMin);
					yMax = Math.max(node.y + node.r, yMax);
				}
				nodes.forEach(d3_layout_packLink);
				a = nodes[0];
				a.x = -a.r;
				a.y = 0;
				bound(a);
				if(n > 1) {
					b = nodes[1];
					b.x = b.r;
					b.y = 0;
					bound(b);
					if(n > 2) {
						c = nodes[2];
						d3_layout_packPlace(a, b, c);
						bound(c);
						d3_layout_packInsert(a, c);
						a._pack_prev = c;
						d3_layout_packInsert(c, b);
						b = a._pack_next;
						for(i = 3; i < n; i++) {
							d3_layout_packPlace(a, b, c = nodes[i]);
							var isect = 0,
								s1 = 1,
								s2 = 1;
							for(j = b._pack_next; j !== b; j = j._pack_next, s1++) {
								if(d3_layout_packIntersects(j, c)) {
									isect = 1;
									break;
								}
							}
							if(isect == 1) {
								for(k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) {
									if(d3_layout_packIntersects(k, c)) {
										break;
									}
								}
							}
							if(isect) {
								if(s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j);
								else d3_layout_packSplice(a = k, b);
								i--;
							} else {
								d3_layout_packInsert(a, c);
								b = c;
								bound(c);
							}
						}
					}
				}
				var cx = (xMin + xMax) / 2,
					cy = (yMin + yMax) / 2,
					cr = 0;
				for(i = 0; i < n; i++) {
					c = nodes[i];
					c.x -= cx;
					c.y -= cy;
					cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y));
				}
				node.r = cr;
				nodes.forEach(d3_layout_packUnlink);
			}

			function d3_layout_packLink(node) {
				node._pack_next = node._pack_prev = node;
			}

			function d3_layout_packUnlink(node) {
				delete node._pack_next;
				delete node._pack_prev;
			}

			function d3_layout_packTransform(node, x, y, k) {
				var children = node.children;
				node.x = x += k * node.x;
				node.y = y += k * node.y;
				node.r *= k;
				if(children) {
					var i = -1,
						n = children.length;
					while(++i < n) d3_layout_packTransform(children[i], x, y, k);
				}
			}

			function d3_layout_packPlace(a, b, c) {
				var db = a.r + c.r,
					dx = b.x - a.x,
					dy = b.y - a.y;
				if(db && (dx || dy)) {
					var da = b.r + c.r,
						dc = dx * dx + dy * dy;
					da *= da;
					db *= db;
					var x = .5 + (db - da) / (2 * dc),
						y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc);
					c.x = a.x + x * dx + y * dy;
					c.y = a.y + x * dy - y * dx;
				} else {
					c.x = a.x + db;
					c.y = a.y;
				}
			}
			d3.layout.tree = function() {
				var hierarchy = d3.layout.hierarchy().sort(null).value(null),
					separation = d3_layout_treeSeparation,
					size = [1, 1],
					nodeSize = null;

				function tree(d, i) {
					var nodes = hierarchy.call(this, d, i),
						root0 = nodes[0],
						root1 = wrapTree(root0);
					d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z;
					d3_layout_hierarchyVisitBefore(root1, secondWalk);
					if(nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode);
					else {
						var left = root0,
							right = root0,
							bottom = root0;
						d3_layout_hierarchyVisitBefore(root0, function(node) {
							if(node.x < left.x) left = node;
							if(node.x > right.x) right = node;
							if(node.depth > bottom.depth) bottom = node;
						});
						var tx = separation(left, right) / 2 - left.x,
							kx = size[0] / (right.x + separation(right, left) / 2 + tx),
							ky = size[1] / (bottom.depth || 1);
						d3_layout_hierarchyVisitBefore(root0, function(node) {
							node.x = (node.x + tx) * kx;
							node.y = node.depth * ky;
						});
					}
					return nodes;
				}

				function wrapTree(root0) {
					var root1 = {
							A: null,
							children: [root0]
						},
						queue = [root1],
						node1;
					while((node1 = queue.pop()) != null) {
						for(var children = node1.children, child, i = 0, n = children.length; i < n; ++i) {
							queue.push((children[i] = child = {
								_: children[i],
								parent: node1,
								children: (child = children[i].children) && child.slice() || [],
								A: null,
								a: null,
								z: 0,
								m: 0,
								c: 0,
								s: 0,
								t: null,
								i: i
							}).a = child);
						}
					}
					return root1.children[0];
				}

				function firstWalk(v) {
					var children = v.children,
						siblings = v.parent.children,
						w = v.i ? siblings[v.i - 1] : null;
					if(children.length) {
						d3_layout_treeShift(v);
						var midpoint = (children[0].z + children[children.length - 1].z) / 2;
						if(w) {
							v.z = w.z + separation(v._, w._);
							v.m = v.z - midpoint;
						} else {
							v.z = midpoint;
						}
					} else if(w) {
						v.z = w.z + separation(v._, w._);
					}
					v.parent.A = apportion(v, w, v.parent.A || siblings[0]);
				}

				function secondWalk(v) {
					v._.x = v.z + v.parent.m;
					v.m += v.parent.m;
				}

				function apportion(v, w, ancestor) {
					if(w) {
						var vip = v,
							vop = v,
							vim = w,
							vom = vip.parent.children[0],
							sip = vip.m,
							sop = vop.m,
							sim = vim.m,
							som = vom.m,
							shift;
						while(vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) {
							vom = d3_layout_treeLeft(vom);
							vop = d3_layout_treeRight(vop);
							vop.a = v;
							shift = vim.z + sim - vip.z - sip + separation(vim._, vip._);
							if(shift > 0) {
								d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift);
								sip += shift;
								sop += shift;
							}
							sim += vim.m;
							sip += vip.m;
							som += vom.m;
							sop += vop.m;
						}
						if(vim && !d3_layout_treeRight(vop)) {
							vop.t = vim;
							vop.m += sim - sop;
						}
						if(vip && !d3_layout_treeLeft(vom)) {
							vom.t = vip;
							vom.m += sip - som;
							ancestor = v;
						}
					}
					return ancestor;
				}

				function sizeNode(node) {
					node.x *= size[0];
					node.y = node.depth * size[1];
				}
				tree.separation = function(x) {
					if(!arguments.length) return separation;
					separation = x;
					return tree;
				};
				tree.size = function(x) {
					if(!arguments.length) return nodeSize ? null : size;
					nodeSize = (size = x) == null ? sizeNode : null;
					return tree;
				};
				tree.nodeSize = function(x) {
					if(!arguments.length) return nodeSize ? size : null;
					nodeSize = (size = x) == null ? null : sizeNode;
					return tree;
				};
				return d3_layout_hierarchyRebind(tree, hierarchy);
			};

			function d3_layout_treeSeparation(a, b) {
				return a.parent == b.parent ? 1 : 2;
			}

			function d3_layout_treeLeft(v) {
				var children = v.children;
				return children.length ? children[0] : v.t;
			}

			function d3_layout_treeRight(v) {
				var children = v.children,
					n;
				return(n = children.length) ? children[n - 1] : v.t;
			}

			function d3_layout_treeMove(wm, wp, shift) {
				var change = shift / (wp.i - wm.i);
				wp.c -= change;
				wp.s += shift;
				wm.c += change;
				wp.z += shift;
				wp.m += shift;
			}

			function d3_layout_treeShift(v) {
				var shift = 0,
					change = 0,
					children = v.children,
					i = children.length,
					w;
				while(--i >= 0) {
					w = children[i];
					w.z += shift;
					w.m += shift;
					shift += w.s + (change += w.c);
				}
			}

			function d3_layout_treeAncestor(vim, v, ancestor) {
				return vim.a.parent === v.parent ? vim.a : ancestor;
			}
			d3.layout.cluster = function() {
				var hierarchy = d3.layout.hierarchy().sort(null).value(null),
					separation = d3_layout_treeSeparation,
					size = [1, 1],
					nodeSize = false;

				function cluster(d, i) {
					var nodes = hierarchy.call(this, d, i),
						root = nodes[0],
						previousNode, x = 0;
					d3_layout_hierarchyVisitAfter(root, function(node) {
						var children = node.children;
						if(children && children.length) {
							node.x = d3_layout_clusterX(children);
							node.y = d3_layout_clusterY(children);
						} else {
							node.x = previousNode ? x += separation(node, previousNode) : 0;
							node.y = 0;
							previousNode = node;
						}
					});
					var left = d3_layout_clusterLeft(root),
						right = d3_layout_clusterRight(root),
						x0 = left.x - separation(left, right) / 2,
						x1 = right.x + separation(right, left) / 2;
					d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) {
						node.x = (node.x - root.x) * size[0];
						node.y = (root.y - node.y) * size[1];
					} : function(node) {
						node.x = (node.x - x0) / (x1 - x0) * size[0];
						node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1];
					});
					return nodes;
				}
				cluster.separation = function(x) {
					if(!arguments.length) return separation;
					separation = x;
					return cluster;
				};
				cluster.size = function(x) {
					if(!arguments.length) return nodeSize ? null : size;
					nodeSize = (size = x) == null;
					return cluster;
				};
				cluster.nodeSize = function(x) {
					if(!arguments.length) return nodeSize ? size : null;
					nodeSize = (size = x) != null;
					return cluster;
				};
				return d3_layout_hierarchyRebind(cluster, hierarchy);
			};

			function d3_layout_clusterY(children) {
				return 1 + d3.max(children, function(child) {
					return child.y;
				});
			}

			function d3_layout_clusterX(children) {
				return children.reduce(function(x, child) {
					return x + child.x;
				}, 0) / children.length;
			}

			function d3_layout_clusterLeft(node) {
				var children = node.children;
				return children && children.length ? d3_layout_clusterLeft(children[0]) : node;
			}

			function d3_layout_clusterRight(node) {
				var children = node.children,
					n;
				return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node;
			}
			d3.layout.treemap = function() {
				var hierarchy = d3.layout.hierarchy(),
					round = Math.round,
					size = [1, 1],
					padding = null,
					pad = d3_layout_treemapPadNull,
					sticky = false,
					stickies, mode = "squarify",
					ratio = .5 * (1 + Math.sqrt(5));

				function scale(children, k) {
					var i = -1,
						n = children.length,
						child, area;
					while(++i < n) {
						area = (child = children[i]).value * (k < 0 ? 0 : k);
						child.area = isNaN(area) || area <= 0 ? 0 : area;
					}
				}

				function squarify(node) {
					var children = node.children;
					if(children && children.length) {
						var rect = pad(node),
							row = [],
							remaining = children.slice(),
							child, best = Infinity,
							score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy),
							n;
						scale(remaining, rect.dx * rect.dy / node.value);
						row.area = 0;
						while((n = remaining.length) > 0) {
							row.push(child = remaining[n - 1]);
							row.area += child.area;
							if(mode !== "squarify" || (score = worst(row, u)) <= best) {
								remaining.pop();
								best = score;
							} else {
								row.area -= row.pop().area;
								position(row, u, rect, false);
								u = Math.min(rect.dx, rect.dy);
								row.length = row.area = 0;
								best = Infinity;
							}
						}
						if(row.length) {
							position(row, u, rect, true);
							row.length = row.area = 0;
						}
						children.forEach(squarify);
					}
				}

				function stickify(node) {
					var children = node.children;
					if(children && children.length) {
						var rect = pad(node),
							remaining = children.slice(),
							child, row = [];
						scale(remaining, rect.dx * rect.dy / node.value);
						row.area = 0;
						while(child = remaining.pop()) {
							row.push(child);
							row.area += child.area;
							if(child.z != null) {
								position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length);
								row.length = row.area = 0;
							}
						}
						children.forEach(stickify);
					}
				}

				function worst(row, u) {
					var s = row.area,
						r, rmax = 0,
						rmin = Infinity,
						i = -1,
						n = row.length;
					while(++i < n) {
						if(!(r = row[i].area)) continue;
						if(r < rmin) rmin = r;
						if(r > rmax) rmax = r;
					}
					s *= s;
					u *= u;
					return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity;
				}

				function position(row, u, rect, flush) {
					var i = -1,
						n = row.length,
						x = rect.x,
						y = rect.y,
						v = u ? round(row.area / u) : 0,
						o;
					if(u == rect.dx) {
						if(flush || v > rect.dy) v = rect.dy;
						while(++i < n) {
							o = row[i];
							o.x = x;
							o.y = y;
							o.dy = v;
							x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0);
						}
						o.z = true;
						o.dx += rect.x + rect.dx - x;
						rect.y += v;
						rect.dy -= v;
					} else {
						if(flush || v > rect.dx) v = rect.dx;
						while(++i < n) {
							o = row[i];
							o.x = x;
							o.y = y;
							o.dx = v;
							y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0);
						}
						o.z = false;
						o.dy += rect.y + rect.dy - y;
						rect.x += v;
						rect.dx -= v;
					}
				}

				function treemap(d) {
					var nodes = stickies || hierarchy(d),
						root = nodes[0];
					root.x = 0;
					root.y = 0;
					root.dx = size[0];
					root.dy = size[1];
					if(stickies) hierarchy.revalue(root);
					scale([root], root.dx * root.dy / root.value);
					(stickies ? stickify : squarify)(root);
					if(sticky) stickies = nodes;
					return nodes;
				}
				treemap.size = function(x) {
					if(!arguments.length) return size;
					size = x;
					return treemap;
				};
				treemap.padding = function(x) {
					if(!arguments.length) return padding;

					function padFunction(node) {
						var p = x.call(treemap, node, node.depth);
						return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [p, p, p, p] : p);
					}

					function padConstant(node) {
						return d3_layout_treemapPad(node, x);
					}
					var type;
					pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [x, x, x, x],
						padConstant) : padConstant;
					return treemap;
				};
				treemap.round = function(x) {
					if(!arguments.length) return round != Number;
					round = x ? Math.round : Number;
					return treemap;
				};
				treemap.sticky = function(x) {
					if(!arguments.length) return sticky;
					sticky = x;
					stickies = null;
					return treemap;
				};
				treemap.ratio = function(x) {
					if(!arguments.length) return ratio;
					ratio = x;
					return treemap;
				};
				treemap.mode = function(x) {
					if(!arguments.length) return mode;
					mode = x + "";
					return treemap;
				};
				return d3_layout_hierarchyRebind(treemap, hierarchy);
			};

			function d3_layout_treemapPadNull(node) {
				return {
					x: node.x,
					y: node.y,
					dx: node.dx,
					dy: node.dy
				};
			}

			function d3_layout_treemapPad(node, padding) {
				var x = node.x + padding[3],
					y = node.y + padding[0],
					dx = node.dx - padding[1] - padding[3],
					dy = node.dy - padding[0] - padding[2];
				if(dx < 0) {
					x += dx / 2;
					dx = 0;
				}
				if(dy < 0) {
					y += dy / 2;
					dy = 0;
				}
				return {
					x: x,
					y: y,
					dx: dx,
					dy: dy
				};
			}
			d3.random = {
				normal: function(µ, σ) {
					var n = arguments.length;
					if(n < 2) σ = 1;
					if(n < 1) µ = 0;
					return function() {
						var x, y, r;
						do {
							x = Math.random() * 2 - 1;
							y = Math.random() * 2 - 1;
							r = x * x + y * y;
						} while (!r || r > 1);
						return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r);
					};
				},
				logNormal: function() {
					var random = d3.random.normal.apply(d3, arguments);
					return function() {
						return Math.exp(random());
					};
				},
				bates: function(m) {
					var random = d3.random.irwinHall(m);
					return function() {
						return random() / m;
					};
				},
				irwinHall: function(m) {
					return function() {
						for(var s = 0, j = 0; j < m; j++) s += Math.random();
						return s;
					};
				}
			};
			d3.scale = {};

			function d3_scaleExtent(domain) {
				var start = domain[0],
					stop = domain[domain.length - 1];
				return start < stop ? [start, stop] : [stop, start];
			}

			function d3_scaleRange(scale) {
				return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
			}

			function d3_scale_bilinear(domain, range, uninterpolate, interpolate) {
				var u = uninterpolate(domain[0], domain[1]),
					i = interpolate(range[0], range[1]);
				return function(x) {
					return i(u(x));
				};
			}

			function d3_scale_nice(domain, nice) {
				var i0 = 0,
					i1 = domain.length - 1,
					x0 = domain[i0],
					x1 = domain[i1],
					dx;
				if(x1 < x0) {
					dx = i0, i0 = i1, i1 = dx;
					dx = x0, x0 = x1, x1 = dx;
				}
				domain[i0] = nice.floor(x0);
				domain[i1] = nice.ceil(x1);
				return domain;
			}

			function d3_scale_niceStep(step) {
				return step ? {
					floor: function(x) {
						return Math.floor(x / step) * step;
					},
					ceil: function(x) {
						return Math.ceil(x / step) * step;
					}
				} : d3_scale_niceIdentity;
			}
			var d3_scale_niceIdentity = {
				floor: d3_identity,
				ceil: d3_identity
			};

			function d3_scale_polylinear(domain, range, uninterpolate, interpolate) {
				var u = [],
					i = [],
					j = 0,
					k = Math.min(domain.length, range.length) - 1;
				if(domain[k] < domain[0]) {
					domain = domain.slice().reverse();
					range = range.slice().reverse();
				}
				while(++j <= k) {
					u.push(uninterpolate(domain[j - 1], domain[j]));
					i.push(interpolate(range[j - 1], range[j]));
				}
				return function(x) {
					var j = d3.bisect(domain, x, 1, k) - 1;
					return i[j](u[j](x));
				};
			}
			d3.scale.linear = function() {
				return d3_scale_linear([0, 1], [0, 1], d3_interpolate, false);
			};

			function d3_scale_linear(domain, range, interpolate, clamp) {
				var output, input;

				function rescale() {
					var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear,
						uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber;
					output = linear(domain, range, uninterpolate, interpolate);
					input = linear(range, domain, uninterpolate, d3_interpolate);
					return scale;
				}

				function scale(x) {
					return output(x);
				}
				scale.invert = function(y) {
					return input(y);
				};
				scale.domain = function(x) {
					if(!arguments.length) return domain;
					domain = x.map(Number);
					return rescale();
				};
				scale.range = function(x) {
					if(!arguments.length) return range;
					range = x;
					return rescale();
				};
				scale.rangeRound = function(x) {
					return scale.range(x).interpolate(d3_interpolateRound);
				};
				scale.clamp = function(x) {
					if(!arguments.length) return clamp;
					clamp = x;
					return rescale();
				};
				scale.interpolate = function(x) {
					if(!arguments.length) return interpolate;
					interpolate = x;
					return rescale();
				};
				scale.ticks = function(m) {
					return d3_scale_linearTicks(domain, m);
				};
				scale.tickFormat = function(m, format) {
					return d3_scale_linearTickFormat(domain, m, format);
				};
				scale.nice = function(m) {
					d3_scale_linearNice(domain, m);
					return rescale();
				};
				scale.copy = function() {
					return d3_scale_linear(domain, range, interpolate, clamp);
				};
				return rescale();
			}

			function d3_scale_linearRebind(scale, linear) {
				return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
			}

			function d3_scale_linearNice(domain, m) {
				return d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
			}

			function d3_scale_linearTickRange(domain, m) {
				if(m == null) m = 10;
				var extent = d3_scaleExtent(domain),
					span = extent[1] - extent[0],
					step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)),
					err = m / span * step;
				if(err <= .15) step *= 10;
				else if(err <= .35) step *= 5;
				else if(err <= .75) step *= 2;
				extent[0] = Math.ceil(extent[0] / step) * step;
				extent[1] = Math.floor(extent[1] / step) * step + step * .5;
				extent[2] = step;
				return extent;
			}

			function d3_scale_linearTicks(domain, m) {
				return d3.range.apply(d3, d3_scale_linearTickRange(domain, m));
			}

			function d3_scale_linearTickFormat(domain, m, format) {
				var range = d3_scale_linearTickRange(domain, m);
				if(format) {
					var match = d3_format_re.exec(format);
					match.shift();
					if(match[8] === "s") {
						var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1])));
						if(!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2]));
						match[8] = "f";
						format = d3.format(match.join(""));
						return function(d) {
							return format(prefix.scale(d)) + prefix.symbol;
						};
					}
					if(!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range);
					format = match.join("");
				} else {
					format = ",." + d3_scale_linearPrecision(range[2]) + "f";
				}
				return d3.format(format);
			}
			var d3_scale_linearFormatSignificant = {
				s: 1,
				g: 1,
				p: 1,
				r: 1,
				e: 1
			};

			function d3_scale_linearPrecision(value) {
				return -Math.floor(Math.log(value) / Math.LN10 + .01);
			}

			function d3_scale_linearFormatPrecision(type, range) {
				var p = d3_scale_linearPrecision(range[2]);
				return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2;
			}
			d3.scale.log = function() {
				return d3_scale_log(d3.scale.linear().domain([0, 1]), 10, true, [1, 10]);
			};

			function d3_scale_log(linear, base, positive, domain) {
				function log(x) {
					return(positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base);
				}

				function pow(x) {
					return positive ? Math.pow(base, x) : -Math.pow(base, -x);
				}

				function scale(x) {
					return linear(log(x));
				}
				scale.invert = function(x) {
					return pow(linear.invert(x));
				};
				scale.domain = function(x) {
					if(!arguments.length) return domain;
					positive = x[0] >= 0;
					linear.domain((domain = x.map(Number)).map(log));
					return scale;
				};
				scale.base = function(_) {
					if(!arguments.length) return base;
					base = +_;
					linear.domain(domain.map(log));
					return scale;
				};
				scale.nice = function() {
					var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative);
					linear.domain(niced);
					domain = niced.map(pow);
					return scale;
				};
				scale.ticks = function() {
					var extent = d3_scaleExtent(domain),
						ticks = [],
						u = extent[0],
						v = extent[1],
						i = Math.floor(log(u)),
						j = Math.ceil(log(v)),
						n = base % 1 ? 2 : base;
					if(isFinite(j - i)) {
						if(positive) {
							for(; i < j; i++)
								for(var k = 1; k < n; k++) ticks.push(pow(i) * k);
							ticks.push(pow(i));
						} else {
							ticks.push(pow(i));
							for(; i++ < j;)
								for(var k = n - 1; k > 0; k--) ticks.push(pow(i) * k);
						}
						for(i = 0; ticks[i] < u; i++) {}
						for(j = ticks.length; ticks[j - 1] > v; j--) {}
						ticks = ticks.slice(i, j);
					}
					return ticks;
				};
				scale.tickFormat = function(n, format) {
					if(!arguments.length) return d3_scale_logFormat;
					if(arguments.length < 2) format = d3_scale_logFormat;
					else if(typeof format !== "function") format = d3.format(format);
					var k = Math.max(.1, n / scale.ticks().length),
						f = positive ? (e = 1e-12, Math.ceil) : (e = -1e-12,
							Math.floor),
						e;
					return function(d) {
						return d / pow(f(log(d) + e)) <= k ? format(d) : "";
					};
				};
				scale.copy = function() {
					return d3_scale_log(linear.copy(), base, positive, domain);
				};
				return d3_scale_linearRebind(scale, linear);
			}
			var d3_scale_logFormat = d3.format(".0e"),
				d3_scale_logNiceNegative = {
					floor: function(x) {
						return -Math.ceil(-x);
					},
					ceil: function(x) {
						return -Math.floor(-x);
					}
				};
			d3.scale.pow = function() {
				return d3_scale_pow(d3.scale.linear(), 1, [0, 1]);
			};

			function d3_scale_pow(linear, exponent, domain) {
				var powp = d3_scale_powPow(exponent),
					powb = d3_scale_powPow(1 / exponent);

				function scale(x) {
					return linear(powp(x));
				}
				scale.invert = function(x) {
					return powb(linear.invert(x));
				};
				scale.domain = function(x) {
					if(!arguments.length) return domain;
					linear.domain((domain = x.map(Number)).map(powp));
					return scale;
				};
				scale.ticks = function(m) {
					return d3_scale_linearTicks(domain, m);
				};
				scale.tickFormat = function(m, format) {
					return d3_scale_linearTickFormat(domain, m, format);
				};
				scale.nice = function(m) {
					return scale.domain(d3_scale_linearNice(domain, m));
				};
				scale.exponent = function(x) {
					if(!arguments.length) return exponent;
					powp = d3_scale_powPow(exponent = x);
					powb = d3_scale_powPow(1 / exponent);
					linear.domain(domain.map(powp));
					return scale;
				};
				scale.copy = function() {
					return d3_scale_pow(linear.copy(), exponent, domain);
				};
				return d3_scale_linearRebind(scale, linear);
			}

			function d3_scale_powPow(e) {
				return function(x) {
					return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e);
				};
			}
			d3.scale.sqrt = function() {
				return d3.scale.pow().exponent(.5);
			};
			d3.scale.ordinal = function() {
				return d3_scale_ordinal([], {
					t: "range",
					a: [
						[]
					]
				});
			};

			function d3_scale_ordinal(domain, ranger) {
				var index, range, rangeBand;

				function scale(x) {
					return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length];
				}

				function steps(start, step) {
					return d3.range(domain.length).map(function(i) {
						return start + step * i;
					});
				}
				scale.domain = function(x) {
					if(!arguments.length) return domain;
					domain = [];
					index = new d3_Map();
					var i = -1,
						n = x.length,
						xi;
					while(++i < n)
						if(!index.has(xi = x[i])) index.set(xi, domain.push(xi));
					return scale[ranger.t].apply(scale, ranger.a);
				};
				scale.range = function(x) {
					if(!arguments.length) return range;
					range = x;
					rangeBand = 0;
					ranger = {
						t: "range",
						a: arguments
					};
					return scale;
				};
				scale.rangePoints = function(x, padding) {
					if(arguments.length < 2) padding = 0;
					var start = x[0],
						stop = x[1],
						step = domain.length < 2 ? (start = (start + stop) / 2,
							0) : (stop - start) / (domain.length - 1 + padding);
					range = steps(start + step * padding / 2, step);
					rangeBand = 0;
					ranger = {
						t: "rangePoints",
						a: arguments
					};
					return scale;
				};
				scale.rangeRoundPoints = function(x, padding) {
					if(arguments.length < 2) padding = 0;
					var start = x[0],
						stop = x[1],
						step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2),
							0) : (stop - start) / (domain.length - 1 + padding) | 0;
					range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step);
					rangeBand = 0;
					ranger = {
						t: "rangeRoundPoints",
						a: arguments
					};
					return scale;
				};
				scale.rangeBands = function(x, padding, outerPadding) {
					if(arguments.length < 2) padding = 0;
					if(arguments.length < 3) outerPadding = padding;
					var reverse = x[1] < x[0],
						start = x[reverse - 0],
						stop = x[1 - reverse],
						step = (stop - start) / (domain.length - padding + 2 * outerPadding);
					range = steps(start + step * outerPadding, step);
					if(reverse) range.reverse();
					rangeBand = step * (1 - padding);
					ranger = {
						t: "rangeBands",
						a: arguments
					};
					return scale;
				};
				scale.rangeRoundBands = function(x, padding, outerPadding) {
					if(arguments.length < 2) padding = 0;
					if(arguments.length < 3) outerPadding = padding;
					var reverse = x[1] < x[0],
						start = x[reverse - 0],
						stop = x[1 - reverse],
						step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding));
					range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step);
					if(reverse) range.reverse();
					rangeBand = Math.round(step * (1 - padding));
					ranger = {
						t: "rangeRoundBands",
						a: arguments
					};
					return scale;
				};
				scale.rangeBand = function() {
					return rangeBand;
				};
				scale.rangeExtent = function() {
					return d3_scaleExtent(ranger.a[0]);
				};
				scale.copy = function() {
					return d3_scale_ordinal(domain, ranger);
				};
				return scale.domain(domain);
			}
			d3.scale.category10 = function() {
				return d3.scale.ordinal().range(d3_category10);
			};
			d3.scale.category20 = function() {
				return d3.scale.ordinal().range(d3_category20);
			};
			d3.scale.category20b = function() {
				return d3.scale.ordinal().range(d3_category20b);
			};
			d3.scale.category20c = function() {
				return d3.scale.ordinal().range(d3_category20c);
			};
			var d3_category10 = [2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175].map(d3_rgbString);
			var d3_category20 = [2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725].map(d3_rgbString);
			var d3_category20b = [3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654].map(d3_rgbString);
			var d3_category20c = [3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081].map(d3_rgbString);
			d3.scale.quantile = function() {
				return d3_scale_quantile([], []);
			};

			function d3_scale_quantile(domain, range) {
				var thresholds;

				function rescale() {
					var k = 0,
						q = range.length;
					thresholds = [];
					while(++k < q) thresholds[k - 1] = d3.quantile(domain, k / q);
					return scale;
				}

				function scale(x) {
					if(!isNaN(x = +x)) return range[d3.bisect(thresholds, x)];
				}
				scale.domain = function(x) {
					if(!arguments.length) return domain;
					domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending);
					return rescale();
				};
				scale.range = function(x) {
					if(!arguments.length) return range;
					range = x;
					return rescale();
				};
				scale.quantiles = function() {
					return thresholds;
				};
				scale.invertExtent = function(y) {
					y = range.indexOf(y);
					return y < 0 ? [NaN, NaN] : [y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1]];
				};
				scale.copy = function() {
					return d3_scale_quantile(domain, range);
				};
				return rescale();
			}
			d3.scale.quantize = function() {
				return d3_scale_quantize(0, 1, [0, 1]);
			};

			function d3_scale_quantize(x0, x1, range) {
				var kx, i;

				function scale(x) {
					return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))];
				}

				function rescale() {
					kx = range.length / (x1 - x0);
					i = range.length - 1;
					return scale;
				}
				scale.domain = function(x) {
					if(!arguments.length) return [x0, x1];
					x0 = +x[0];
					x1 = +x[x.length - 1];
					return rescale();
				};
				scale.range = function(x) {
					if(!arguments.length) return range;
					range = x;
					return rescale();
				};
				scale.invertExtent = function(y) {
					y = range.indexOf(y);
					y = y < 0 ? NaN : y / kx + x0;
					return [y, y + 1 / kx];
				};
				scale.copy = function() {
					return d3_scale_quantize(x0, x1, range);
				};
				return rescale();
			}
			d3.scale.threshold = function() {
				return d3_scale_threshold([.5], [0, 1]);
			};

			function d3_scale_threshold(domain, range) {
				function scale(x) {
					if(x <= x) return range[d3.bisect(domain, x)];
				}
				scale.domain = function(_) {
					if(!arguments.length) return domain;
					domain = _;
					return scale;
				};
				scale.range = function(_) {
					if(!arguments.length) return range;
					range = _;
					return scale;
				};
				scale.invertExtent = function(y) {
					y = range.indexOf(y);
					return [domain[y - 1], domain[y]];
				};
				scale.copy = function() {
					return d3_scale_threshold(domain, range);
				};
				return scale;
			}
			d3.scale.identity = function() {
				return d3_scale_identity([0, 1]);
			};

			function d3_scale_identity(domain) {
				function identity(x) {
					return +x;
				}
				identity.invert = identity;
				identity.domain = identity.range = function(x) {
					if(!arguments.length) return domain;
					domain = x.map(identity);
					return identity;
				};
				identity.ticks = function(m) {
					return d3_scale_linearTicks(domain, m);
				};
				identity.tickFormat = function(m, format) {
					return d3_scale_linearTickFormat(domain, m, format);
				};
				identity.copy = function() {
					return d3_scale_identity(domain);
				};
				return identity;
			}
			d3.svg = {};

			function d3_zero() {
				return 0;
			}
			d3.svg.arc = function() {
				var innerRadius = d3_svg_arcInnerRadius,
					outerRadius = d3_svg_arcOuterRadius,
					cornerRadius = d3_zero,
					padRadius = d3_svg_arcAuto,
					startAngle = d3_svg_arcStartAngle,
					endAngle = d3_svg_arcEndAngle,
					padAngle = d3_svg_arcPadAngle;

				function arc() {
					var r0 = Math.max(0, +innerRadius.apply(this, arguments)),
						r1 = Math.max(0, +outerRadius.apply(this, arguments)),
						a0 = startAngle.apply(this, arguments) - halfπ,
						a1 = endAngle.apply(this, arguments) - halfπ,
						da = Math.abs(a1 - a0),
						cw = a0 > a1 ? 0 : 1;
					if(r1 < r0) rc = r1, r1 = r0, r0 = rc;
					if(da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z";
					var rc, cr, rp, ap, p0 = 0,
						p1 = 0,
						x0, y0, x1, y1, x2, y2, x3, y3, path = [];
					if(ap = (+padAngle.apply(this, arguments) || 0) / 2) {
						rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments);
						if(!cw) p1 *= -1;
						if(r1) p1 = d3_asin(rp / r1 * Math.sin(ap));
						if(r0) p0 = d3_asin(rp / r0 * Math.sin(ap));
					}
					if(r1) {
						x0 = r1 * Math.cos(a0 + p1);
						y0 = r1 * Math.sin(a0 + p1);
						x1 = r1 * Math.cos(a1 - p1);
						y1 = r1 * Math.sin(a1 - p1);
						var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1;
						if(p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) {
							var h1 = (a0 + a1) / 2;
							x0 = r1 * Math.cos(h1);
							y0 = r1 * Math.sin(h1);
							x1 = y1 = null;
						}
					} else {
						x0 = y0 = 0;
					}
					if(r0) {
						x2 = r0 * Math.cos(a1 - p0);
						y2 = r0 * Math.sin(a1 - p0);
						x3 = r0 * Math.cos(a0 + p0);
						y3 = r0 * Math.sin(a0 + p0);
						var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1;
						if(p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) {
							var h0 = (a0 + a1) / 2;
							x2 = r0 * Math.cos(h0);
							y2 = r0 * Math.sin(h0);
							x3 = y3 = null;
						}
					} else {
						x2 = y2 = 0;
					}
					if((rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) {
						cr = r0 < r1 ^ cw ? 0 : 1;
						var oc = x3 == null ? [x2, y2] : x1 == null ? [x0, y0] : d3_geom_polygonIntersect([x0, y0], [x3, y3], [x1, y1], [x2, y2]),
							ax = x0 - oc[0],
							ay = y0 - oc[1],
							bx = x1 - oc[0],
							by = y1 - oc[1],
							kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2),
							lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]);
						if(x1 != null) {
							var rc1 = Math.min(rc, (r1 - lc) / (kc + 1)),
								t30 = d3_svg_arcCornerTangents(x3 == null ? [x2, y2] : [x3, y3], [x0, y0], r1, rc1, cw),
								t12 = d3_svg_arcCornerTangents([x1, y1], [x2, y2], r1, rc1, cw);
							if(rc === rc1) {
								path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]);
							} else {
								path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]);
							}
						} else {
							path.push("M", x0, ",", y0);
						}
						if(x3 != null) {
							var rc0 = Math.min(rc, (r0 - lc) / (kc - 1)),
								t03 = d3_svg_arcCornerTangents([x0, y0], [x3, y3], r0, -rc0, cw),
								t21 = d3_svg_arcCornerTangents([x2, y2], x1 == null ? [x0, y0] : [x1, y1], r0, -rc0, cw);
							if(rc === rc0) {
								path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
							} else {
								path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
							}
						} else {
							path.push("L", x2, ",", y2);
						}
					} else {
						path.push("M", x0, ",", y0);
						if(x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1);
						path.push("L", x2, ",", y2);
						if(x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3);
					}
					path.push("Z");
					return path.join("");
				}

				function circleSegment(r1, cw) {
					return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1;
				}
				arc.innerRadius = function(v) {
					if(!arguments.length) return innerRadius;
					innerRadius = d3_functor(v);
					return arc;
				};
				arc.outerRadius = function(v) {
					if(!arguments.length) return outerRadius;
					outerRadius = d3_functor(v);
					return arc;
				};
				arc.cornerRadius = function(v) {
					if(!arguments.length) return cornerRadius;
					cornerRadius = d3_functor(v);
					return arc;
				};
				arc.padRadius = function(v) {
					if(!arguments.length) return padRadius;
					padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v);
					return arc;
				};
				arc.startAngle = function(v) {
					if(!arguments.length) return startAngle;
					startAngle = d3_functor(v);
					return arc;
				};
				arc.endAngle = function(v) {
					if(!arguments.length) return endAngle;
					endAngle = d3_functor(v);
					return arc;
				};
				arc.padAngle = function(v) {
					if(!arguments.length) return padAngle;
					padAngle = d3_functor(v);
					return arc;
				};
				arc.centroid = function() {
					var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2,
						a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ;
					return [Math.cos(a) * r, Math.sin(a) * r];
				};
				return arc;
			};
			var d3_svg_arcAuto = "auto";

			function d3_svg_arcInnerRadius(d) {
				return d.innerRadius;
			}

			function d3_svg_arcOuterRadius(d) {
				return d.outerRadius;
			}

			function d3_svg_arcStartAngle(d) {
				return d.startAngle;
			}

			function d3_svg_arcEndAngle(d) {
				return d.endAngle;
			}

			function d3_svg_arcPadAngle(d) {
				return d && d.padAngle;
			}

			function d3_svg_arcSweep(x0, y0, x1, y1) {
				return(x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1;
			}

			function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) {
				var x01 = p0[0] - p1[0],
					y01 = p0[1] - p1[1],
					lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01),
					ox = lo * y01,
					oy = -lo * x01,
					x1 = p0[0] + ox,
					y1 = p0[1] + oy,
					x2 = p1[0] + ox,
					y2 = p1[1] + oy,
					x3 = (x1 + x2) / 2,
					y3 = (y1 + y2) / 2,
					dx = x2 - x1,
					dy = y2 - y1,
					d2 = dx * dx + dy * dy,
					r = r1 - rc,
					D = x1 * y2 - x2 * y1,
					d = (dy < 0 ? -1 : 1) * Math.sqrt(r * r * d2 - D * D),
					cx0 = (D * dy - dx * d) / d2,
					cy0 = (-D * dx - dy * d) / d2,
					cx1 = (D * dy + dx * d) / d2,
					cy1 = (-D * dx + dy * d) / d2,
					dx0 = cx0 - x3,
					dy0 = cy0 - y3,
					dx1 = cx1 - x3,
					dy1 = cy1 - y3;
				if(dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1;
				return [
					[cx0 - ox, cy0 - oy],
					[cx0 * r1 / r, cy0 * r1 / r]
				];
			}

			function d3_svg_line(projection) {
				var x = d3_geom_pointX,
					y = d3_geom_pointY,
					defined = d3_true,
					interpolate = d3_svg_lineLinear,
					interpolateKey = interpolate.key,
					tension = .7;

				function line(data) {
					var segments = [],
						points = [],
						i = -1,
						n = data.length,
						d, fx = d3_functor(x),
						fy = d3_functor(y);

					function segment() {
						segments.push("M", interpolate(projection(points), tension));
					}
					while(++i < n) {
						if(defined.call(this, d = data[i], i)) {
							points.push([+fx.call(this, d, i), +fy.call(this, d, i)]);
						} else if(points.length) {
							segment();
							points = [];
						}
					}
					if(points.length) segment();
					return segments.length ? segments.join("") : null;
				}
				line.x = function(_) {
					if(!arguments.length) return x;
					x = _;
					return line;
				};
				line.y = function(_) {
					if(!arguments.length) return y;
					y = _;
					return line;
				};
				line.defined = function(_) {
					if(!arguments.length) return defined;
					defined = _;
					return line;
				};
				line.interpolate = function(_) {
					if(!arguments.length) return interpolateKey;
					if(typeof _ === "function") interpolateKey = interpolate = _;
					else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
					return line;
				};
				line.tension = function(_) {
					if(!arguments.length) return tension;
					tension = _;
					return line;
				};
				return line;
			}
			d3.svg.line = function() {
				return d3_svg_line(d3_identity);
			};
			var d3_svg_lineInterpolators = d3.map({
				linear: d3_svg_lineLinear,
				"linear-closed": d3_svg_lineLinearClosed,
				step: d3_svg_lineStep,
				"step-before": d3_svg_lineStepBefore,
				"step-after": d3_svg_lineStepAfter,
				basis: d3_svg_lineBasis,
				"basis-open": d3_svg_lineBasisOpen,
				"basis-closed": d3_svg_lineBasisClosed,
				bundle: d3_svg_lineBundle,
				cardinal: d3_svg_lineCardinal,
				"cardinal-open": d3_svg_lineCardinalOpen,
				"cardinal-closed": d3_svg_lineCardinalClosed,
				monotone: d3_svg_lineMonotone
			});
			d3_svg_lineInterpolators.forEach(function(key, value) {
				value.key = key;
				value.closed = /-closed$/.test(key);
			});

			function d3_svg_lineLinear(points) {
				return points.join("L");
			}

			function d3_svg_lineLinearClosed(points) {
				return d3_svg_lineLinear(points) + "Z";
			}

			function d3_svg_lineStep(points) {
				var i = 0,
					n = points.length,
					p = points[0],
					path = [p[0], ",", p[1]];
				while(++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]);
				if(n > 1) path.push("H", p[0]);
				return path.join("");
			}

			function d3_svg_lineStepBefore(points) {
				var i = 0,
					n = points.length,
					p = points[0],
					path = [p[0], ",", p[1]];
				while(++i < n) path.push("V", (p = points[i])[1], "H", p[0]);
				return path.join("");
			}

			function d3_svg_lineStepAfter(points) {
				var i = 0,
					n = points.length,
					p = points[0],
					path = [p[0], ",", p[1]];
				while(++i < n) path.push("H", (p = points[i])[0], "V", p[1]);
				return path.join("");
			}

			function d3_svg_lineCardinalOpen(points, tension) {
				return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension));
			}

			function d3_svg_lineCardinalClosed(points, tension) {
				return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]),
					points), d3_svg_lineCardinalTangents([points[points.length - 2]].concat(points, [points[1]]), tension));
			}

			function d3_svg_lineCardinal(points, tension) {
				return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension));
			}

			function d3_svg_lineHermite(points, tangents) {
				if(tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) {
					return d3_svg_lineLinear(points);
				}
				var quad = points.length != tangents.length,
					path = "",
					p0 = points[0],
					p = points[1],
					t0 = tangents[0],
					t = t0,
					pi = 1;
				if(quad) {
					path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1];
					p0 = points[1];
					pi = 2;
				}
				if(tangents.length > 1) {
					t = tangents[1];
					p = points[pi];
					pi++;
					path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
					for(var i = 2; i < tangents.length; i++, pi++) {
						p = points[pi];
						t = tangents[i];
						path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
					}
				}
				if(quad) {
					var lp = points[pi];
					path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1];
				}
				return path;
			}

			function d3_svg_lineCardinalTangents(points, tension) {
				var tangents = [],
					a = (1 - tension) / 2,
					p0, p1 = points[0],
					p2 = points[1],
					i = 1,
					n = points.length;
				while(++i < n) {
					p0 = p1;
					p1 = p2;
					p2 = points[i];
					tangents.push([a * (p2[0] - p0[0]), a * (p2[1] - p0[1])]);
				}
				return tangents;
			}

			function d3_svg_lineBasis(points) {
				if(points.length < 3) return d3_svg_lineLinear(points);
				var i = 1,
					n = points.length,
					pi = points[0],
					x0 = pi[0],
					y0 = pi[1],
					px = [x0, x0, x0, (pi = points[1])[0]],
					py = [y0, y0, y0, pi[1]],
					path = [x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)];
				points.push(points[n - 1]);
				while(++i <= n) {
					pi = points[i];
					px.shift();
					px.push(pi[0]);
					py.shift();
					py.push(pi[1]);
					d3_svg_lineBasisBezier(path, px, py);
				}
				points.pop();
				path.push("L", pi);
				return path.join("");
			}

			function d3_svg_lineBasisOpen(points) {
				if(points.length < 4) return d3_svg_lineLinear(points);
				var path = [],
					i = -1,
					n = points.length,
					pi, px = [0],
					py = [0];
				while(++i < 3) {
					pi = points[i];
					px.push(pi[0]);
					py.push(pi[1]);
				}
				path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py));
				--i;
				while(++i < n) {
					pi = points[i];
					px.shift();
					px.push(pi[0]);
					py.shift();
					py.push(pi[1]);
					d3_svg_lineBasisBezier(path, px, py);
				}
				return path.join("");
			}

			function d3_svg_lineBasisClosed(points) {
				var path, i = -1,
					n = points.length,
					m = n + 4,
					pi, px = [],
					py = [];
				while(++i < 4) {
					pi = points[i % n];
					px.push(pi[0]);
					py.push(pi[1]);
				}
				path = [d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)];
				--i;
				while(++i < m) {
					pi = points[i % n];
					px.shift();
					px.push(pi[0]);
					py.shift();
					py.push(pi[1]);
					d3_svg_lineBasisBezier(path, px, py);
				}
				return path.join("");
			}

			function d3_svg_lineBundle(points, tension) {
				var n = points.length - 1;
				if(n) {
					var x0 = points[0][0],
						y0 = points[0][1],
						dx = points[n][0] - x0,
						dy = points[n][1] - y0,
						i = -1,
						p, t;
					while(++i <= n) {
						p = points[i];
						t = i / n;
						p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx);
						p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy);
					}
				}
				return d3_svg_lineBasis(points);
			}

			function d3_svg_lineDot4(a, b) {
				return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
			}
			var d3_svg_lineBasisBezier1 = [0, 2 / 3, 1 / 3, 0],
				d3_svg_lineBasisBezier2 = [0, 1 / 3, 2 / 3, 0],
				d3_svg_lineBasisBezier3 = [0, 1 / 6, 2 / 3, 1 / 6];

			function d3_svg_lineBasisBezier(path, x, y) {
				path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y));
			}

			function d3_svg_lineSlope(p0, p1) {
				return(p1[1] - p0[1]) / (p1[0] - p0[0]);
			}

			function d3_svg_lineFiniteDifferences(points) {
				var i = 0,
					j = points.length - 1,
					m = [],
					p0 = points[0],
					p1 = points[1],
					d = m[0] = d3_svg_lineSlope(p0, p1);
				while(++i < j) {
					m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
				}
				m[i] = d;
				return m;
			}

			function d3_svg_lineMonotoneTangents(points) {
				var tangents = [],
					d, a, b, s, m = d3_svg_lineFiniteDifferences(points),
					i = -1,
					j = points.length - 1;
				while(++i < j) {
					d = d3_svg_lineSlope(points[i], points[i + 1]);
					if(abs(d) < ε) {
						m[i] = m[i + 1] = 0;
					} else {
						a = m[i] / d;
						b = m[i + 1] / d;
						s = a * a + b * b;
						if(s > 9) {
							s = d * 3 / Math.sqrt(s);
							m[i] = s * a;
							m[i + 1] = s * b;
						}
					}
				}
				i = -1;
				while(++i <= j) {
					s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i]));
					tangents.push([s || 0, m[i] * s || 0]);
				}
				return tangents;
			}

			function d3_svg_lineMonotone(points) {
				return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points));
			}
			d3.svg.line.radial = function() {
				var line = d3_svg_line(d3_svg_lineRadial);
				line.radius = line.x, delete line.x;
				line.angle = line.y, delete line.y;
				return line;
			};

			function d3_svg_lineRadial(points) {
				var point, i = -1,
					n = points.length,
					r, a;
				while(++i < n) {
					point = points[i];
					r = point[0];
					a = point[1] - halfπ;
					point[0] = r * Math.cos(a);
					point[1] = r * Math.sin(a);
				}
				return points;
			}

			function d3_svg_area(projection) {
				var x0 = d3_geom_pointX,
					x1 = d3_geom_pointX,
					y0 = 0,
					y1 = d3_geom_pointY,
					defined = d3_true,
					interpolate = d3_svg_lineLinear,
					interpolateKey = interpolate.key,
					interpolateReverse = interpolate,
					L = "L",
					tension = .7;

				function area(data) {
					var segments = [],
						points0 = [],
						points1 = [],
						i = -1,
						n = data.length,
						d, fx0 = d3_functor(x0),
						fy0 = d3_functor(y0),
						fx1 = x0 === x1 ? function() {
							return x;
						} : d3_functor(x1),
						fy1 = y0 === y1 ? function() {
							return y;
						} : d3_functor(y1),
						x, y;

					function segment() {
						segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z");
					}
					while(++i < n) {
						if(defined.call(this, d = data[i], i)) {
							points0.push([x = +fx0.call(this, d, i), y = +fy0.call(this, d, i)]);
							points1.push([+fx1.call(this, d, i), +fy1.call(this, d, i)]);
						} else if(points0.length) {
							segment();
							points0 = [];
							points1 = [];
						}
					}
					if(points0.length) segment();
					return segments.length ? segments.join("") : null;
				}
				area.x = function(_) {
					if(!arguments.length) return x1;
					x0 = x1 = _;
					return area;
				};
				area.x0 = function(_) {
					if(!arguments.length) return x0;
					x0 = _;
					return area;
				};
				area.x1 = function(_) {
					if(!arguments.length) return x1;
					x1 = _;
					return area;
				};
				area.y = function(_) {
					if(!arguments.length) return y1;
					y0 = y1 = _;
					return area;
				};
				area.y0 = function(_) {
					if(!arguments.length) return y0;
					y0 = _;
					return area;
				};
				area.y1 = function(_) {
					if(!arguments.length) return y1;
					y1 = _;
					return area;
				};
				area.defined = function(_) {
					if(!arguments.length) return defined;
					defined = _;
					return area;
				};
				area.interpolate = function(_) {
					if(!arguments.length) return interpolateKey;
					if(typeof _ === "function") interpolateKey = interpolate = _;
					else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
					interpolateReverse = interpolate.reverse || interpolate;
					L = interpolate.closed ? "M" : "L";
					return area;
				};
				area.tension = function(_) {
					if(!arguments.length) return tension;
					tension = _;
					return area;
				};
				return area;
			}
			d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter;
			d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore;
			d3.svg.area = function() {
				return d3_svg_area(d3_identity);
			};
			d3.svg.area.radial = function() {
				var area = d3_svg_area(d3_svg_lineRadial);
				area.radius = area.x, delete area.x;
				area.innerRadius = area.x0, delete area.x0;
				area.outerRadius = area.x1, delete area.x1;
				area.angle = area.y, delete area.y;
				area.startAngle = area.y0, delete area.y0;
				area.endAngle = area.y1, delete area.y1;
				return area;
			};
			d3.svg.chord = function() {
				var source = d3_source,
					target = d3_target,
					radius = d3_svg_chordRadius,
					startAngle = d3_svg_arcStartAngle,
					endAngle = d3_svg_arcEndAngle;

				function chord(d, i) {
					var s = subgroup(this, source, d, i),
						t = subgroup(this, target, d, i);
					return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z";
				}

				function subgroup(self, f, d, i) {
					var subgroup = f.call(self, d, i),
						r = radius.call(self, subgroup, i),
						a0 = startAngle.call(self, subgroup, i) - halfπ,
						a1 = endAngle.call(self, subgroup, i) - halfπ;
					return {
						r: r,
						a0: a0,
						a1: a1,
						p0: [r * Math.cos(a0), r * Math.sin(a0)],
						p1: [r * Math.cos(a1), r * Math.sin(a1)]
					};
				}

				function equals(a, b) {
					return a.a0 == b.a0 && a.a1 == b.a1;
				}

				function arc(r, p, a) {
					return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p;
				}

				function curve(r0, p0, r1, p1) {
					return "Q 0,0 " + p1;
				}
				chord.radius = function(v) {
					if(!arguments.length) return radius;
					radius = d3_functor(v);
					return chord;
				};
				chord.source = function(v) {
					if(!arguments.length) return source;
					source = d3_functor(v);
					return chord;
				};
				chord.target = function(v) {
					if(!arguments.length) return target;
					target = d3_functor(v);
					return chord;
				};
				chord.startAngle = function(v) {
					if(!arguments.length) return startAngle;
					startAngle = d3_functor(v);
					return chord;
				};
				chord.endAngle = function(v) {
					if(!arguments.length) return endAngle;
					endAngle = d3_functor(v);
					return chord;
				};
				return chord;
			};

			function d3_svg_chordRadius(d) {
				return d.radius;
			}
			d3.svg.diagonal = function() {
				var source = d3_source,
					target = d3_target,
					projection = d3_svg_diagonalProjection;

				function diagonal(d, i) {
					var p0 = source.call(this, d, i),
						p3 = target.call(this, d, i),
						m = (p0.y + p3.y) / 2,
						p = [p0, {
							x: p0.x,
							y: m
						}, {
							x: p3.x,
							y: m
						}, p3];
					p = p.map(projection);
					return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
				}
				diagonal.source = function(x) {
					if(!arguments.length) return source;
					source = d3_functor(x);
					return diagonal;
				};
				diagonal.target = function(x) {
					if(!arguments.length) return target;
					target = d3_functor(x);
					return diagonal;
				};
				diagonal.projection = function(x) {
					if(!arguments.length) return projection;
					projection = x;
					return diagonal;
				};
				return diagonal;
			};

			function d3_svg_diagonalProjection(d) {
				return [d.x, d.y];
			}
			d3.svg.diagonal.radial = function() {
				var diagonal = d3.svg.diagonal(),
					projection = d3_svg_diagonalProjection,
					projection_ = diagonal.projection;
				diagonal.projection = function(x) {
					return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection;
				};
				return diagonal;
			};

			function d3_svg_diagonalRadialProjection(projection) {
				return function() {
					var d = projection.apply(this, arguments),
						r = d[0],
						a = d[1] - halfπ;
					return [r * Math.cos(a), r * Math.sin(a)];
				};
			}
			d3.svg.symbol = function() {
				var type = d3_svg_symbolType,
					size = d3_svg_symbolSize;

				function symbol(d, i) {
					return(d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i));
				}
				symbol.type = function(x) {
					if(!arguments.length) return type;
					type = d3_functor(x);
					return symbol;
				};
				symbol.size = function(x) {
					if(!arguments.length) return size;
					size = d3_functor(x);
					return symbol;
				};
				return symbol;
			};

			function d3_svg_symbolSize() {
				return 64;
			}

			function d3_svg_symbolType() {
				return "circle";
			}

			function d3_svg_symbolCircle(size) {
				var r = Math.sqrt(size / π);
				return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z";
			}
			var d3_svg_symbols = d3.map({
				circle: d3_svg_symbolCircle,
				cross: function(size) {
					var r = Math.sqrt(size / 5) / 2;
					return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z";
				},
				diamond: function(size) {
					var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)),
						rx = ry * d3_svg_symbolTan30;
					return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z";
				},
				square: function(size) {
					var r = Math.sqrt(size) / 2;
					return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z";
				},
				"triangle-down": function(size) {
					var rx = Math.sqrt(size / d3_svg_symbolSqrt3),
						ry = rx * d3_svg_symbolSqrt3 / 2;
					return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z";
				},
				"triangle-up": function(size) {
					var rx = Math.sqrt(size / d3_svg_symbolSqrt3),
						ry = rx * d3_svg_symbolSqrt3 / 2;
					return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z";
				}
			});
			d3.svg.symbolTypes = d3_svg_symbols.keys();
			var d3_svg_symbolSqrt3 = Math.sqrt(3),
				d3_svg_symbolTan30 = Math.tan(30 * d3_radians);
			d3_selectionPrototype.transition = function(name) {
				var id = d3_transitionInheritId || ++d3_transitionId,
					ns = d3_transitionNamespace(name),
					subgroups = [],
					subgroup, node, transition = d3_transitionInherit || {
						time: Date.now(),
						ease: d3_ease_cubicInOut,
						delay: 0,
						duration: 250
					};
				for(var j = -1, m = this.length; ++j < m;) {
					subgroups.push(subgroup = []);
					for(var group = this[j], i = -1, n = group.length; ++i < n;) {
						if(node = group[i]) d3_transitionNode(node, i, ns, id, transition);
						subgroup.push(node);
					}
				}
				return d3_transition(subgroups, ns, id);
			};
			d3_selectionPrototype.interrupt = function(name) {
				return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name)));
			};
			var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace());

			function d3_selection_interruptNS(ns) {
				return function() {
					var lock, active;
					if((lock = this[ns]) && (active = lock[lock.active])) {
						if(--lock.count) delete lock[lock.active];
						else delete this[ns];
						lock.active += .5;
						active.event && active.event.interrupt.call(this, this.__data__, active.index);
					}
				};
			}

			function d3_transition(groups, ns, id) {
				d3_subclass(groups, d3_transitionPrototype);
				groups.namespace = ns;
				groups.id = id;
				return groups;
			}
			var d3_transitionPrototype = [],
				d3_transitionId = 0,
				d3_transitionInheritId, d3_transitionInherit;
			d3_transitionPrototype.call = d3_selectionPrototype.call;
			d3_transitionPrototype.empty = d3_selectionPrototype.empty;
			d3_transitionPrototype.node = d3_selectionPrototype.node;
			d3_transitionPrototype.size = d3_selectionPrototype.size;
			d3.transition = function(selection, name) {
				return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3.selection().transition(selection);
			};
			d3.transition.prototype = d3_transitionPrototype;
			d3_transitionPrototype.select = function(selector) {
				var id = this.id,
					ns = this.namespace,
					subgroups = [],
					subgroup, subnode, node;
				selector = d3_selection_selector(selector);
				for(var j = -1, m = this.length; ++j < m;) {
					subgroups.push(subgroup = []);
					for(var group = this[j], i = -1, n = group.length; ++i < n;) {
						if((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) {
							if("__data__" in node) subnode.__data__ = node.__data__;
							d3_transitionNode(subnode, i, ns, id, node[ns][id]);
							subgroup.push(subnode);
						} else {
							subgroup.push(null);
						}
					}
				}
				return d3_transition(subgroups, ns, id);
			};
			d3_transitionPrototype.selectAll = function(selector) {
				var id = this.id,
					ns = this.namespace,
					subgroups = [],
					subgroup, subnodes, node, subnode, transition;
				selector = d3_selection_selectorAll(selector);
				for(var j = -1, m = this.length; ++j < m;) {
					for(var group = this[j], i = -1, n = group.length; ++i < n;) {
						if(node = group[i]) {
							transition = node[ns][id];
							subnodes = selector.call(node, node.__data__, i, j);
							subgroups.push(subgroup = []);
							for(var k = -1, o = subnodes.length; ++k < o;) {
								if(subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition);
								subgroup.push(subnode);
							}
						}
					}
				}
				return d3_transition(subgroups, ns, id);
			};
			d3_transitionPrototype.filter = function(filter) {
				var subgroups = [],
					subgroup, group, node;
				if(typeof filter !== "function") filter = d3_selection_filter(filter);
				for(var j = 0, m = this.length; j < m; j++) {
					subgroups.push(subgroup = []);
					for(var group = this[j], i = 0, n = group.length; i < n; i++) {
						if((node = group[i]) && filter.call(node, node.__data__, i, j)) {
							subgroup.push(node);
						}
					}
				}
				return d3_transition(subgroups, this.namespace, this.id);
			};
			d3_transitionPrototype.tween = function(name, tween) {
				var id = this.id,
					ns = this.namespace;
				if(arguments.length < 2) return this.node()[ns][id].tween.get(name);
				return d3_selection_each(this, tween == null ? function(node) {
					node[ns][id].tween.remove(name);
				} : function(node) {
					node[ns][id].tween.set(name, tween);
				});
			};

			function d3_transition_tween(groups, name, value, tween) {
				var id = groups.id,
					ns = groups.namespace;
				return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) {
					node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j)));
				} : (value = tween(value), function(node) {
					node[ns][id].tween.set(name, value);
				}));
			}
			d3_transitionPrototype.attr = function(nameNS, value) {
				if(arguments.length < 2) {
					for(value in nameNS) this.attr(value, nameNS[value]);
					return this;
				}
				var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate,
					name = d3.ns.qualify(nameNS);

				function attrNull() {
					this.removeAttribute(name);
				}

				function attrNullNS() {
					this.removeAttributeNS(name.space, name.local);
				}

				function attrTween(b) {
					return b == null ? attrNull : (b += "", function() {
						var a = this.getAttribute(name),
							i;
						return a !== b && (i = interpolate(a, b), function(t) {
							this.setAttribute(name, i(t));
						});
					});
				}

				function attrTweenNS(b) {
					return b == null ? attrNullNS : (b += "", function() {
						var a = this.getAttributeNS(name.space, name.local),
							i;
						return a !== b && (i = interpolate(a, b), function(t) {
							this.setAttributeNS(name.space, name.local, i(t));
						});
					});
				}
				return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween);
			};
			d3_transitionPrototype.attrTween = function(nameNS, tween) {
				var name = d3.ns.qualify(nameNS);

				function attrTween(d, i) {
					var f = tween.call(this, d, i, this.getAttribute(name));
					return f && function(t) {
						this.setAttribute(name, f(t));
					};
				}

				function attrTweenNS(d, i) {
					var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
					return f && function(t) {
						this.setAttributeNS(name.space, name.local, f(t));
					};
				}
				return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
			};
			d3_transitionPrototype.style = function(name, value, priority) {
				var n = arguments.length;
				if(n < 3) {
					if(typeof name !== "string") {
						if(n < 2) value = "";
						for(priority in name) this.style(priority, name[priority], value);
						return this;
					}
					priority = "";
				}

				function styleNull() {
					this.style.removeProperty(name);
				}

				function styleString(b) {
					return b == null ? styleNull : (b += "", function() {
						var a = d3_window(this).getComputedStyle(this, null).getPropertyValue(name),
							i;
						return a !== b && (i = d3_interpolate(a, b), function(t) {
							this.style.setProperty(name, i(t), priority);
						});
					});
				}
				return d3_transition_tween(this, "style." + name, value, styleString);
			};
			d3_transitionPrototype.styleTween = function(name, tween, priority) {
				if(arguments.length < 3) priority = "";

				function styleTween(d, i) {
					var f = tween.call(this, d, i, d3_window(this).getComputedStyle(this, null).getPropertyValue(name));
					return f && function(t) {
						this.style.setProperty(name, f(t), priority);
					};
				}
				return this.tween("style." + name, styleTween);
			};
			d3_transitionPrototype.text = function(value) {
				return d3_transition_tween(this, "text", value, d3_transition_text);
			};

			function d3_transition_text(b) {
				if(b == null) b = "";
				return function() {
					this.textContent = b;
				};
			}
			d3_transitionPrototype.remove = function() {
				var ns = this.namespace;
				return this.each("end.transition", function() {
					var p;
					if(this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this);
				});
			};
			d3_transitionPrototype.ease = function(value) {
				var id = this.id,
					ns = this.namespace;
				if(arguments.length < 1) return this.node()[ns][id].ease;
				if(typeof value !== "function") value = d3.ease.apply(d3, arguments);
				return d3_selection_each(this, function(node) {
					node[ns][id].ease = value;
				});
			};
			d3_transitionPrototype.delay = function(value) {
				var id = this.id,
					ns = this.namespace;
				if(arguments.length < 1) return this.node()[ns][id].delay;
				return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
					node[ns][id].delay = +value.call(node, node.__data__, i, j);
				} : (value = +value, function(node) {
					node[ns][id].delay = value;
				}));
			};
			d3_transitionPrototype.duration = function(value) {
				var id = this.id,
					ns = this.namespace;
				if(arguments.length < 1) return this.node()[ns][id].duration;
				return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
					node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j));
				} : (value = Math.max(1, value), function(node) {
					node[ns][id].duration = value;
				}));
			};
			d3_transitionPrototype.each = function(type, listener) {
				var id = this.id,
					ns = this.namespace;
				if(arguments.length < 2) {
					var inherit = d3_transitionInherit,
						inheritId = d3_transitionInheritId;
					try {
						d3_transitionInheritId = id;
						d3_selection_each(this, function(node, i, j) {
							d3_transitionInherit = node[ns][id];
							type.call(node, node.__data__, i, j);
						});
					} finally {
						d3_transitionInherit = inherit;
						d3_transitionInheritId = inheritId;
					}
				} else {
					d3_selection_each(this, function(node) {
						var transition = node[ns][id];
						(transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener);
					});
				}
				return this;
			};
			d3_transitionPrototype.transition = function() {
				var id0 = this.id,
					id1 = ++d3_transitionId,
					ns = this.namespace,
					subgroups = [],
					subgroup, group, node, transition;
				for(var j = 0, m = this.length; j < m; j++) {
					subgroups.push(subgroup = []);
					for(var group = this[j], i = 0, n = group.length; i < n; i++) {
						if(node = group[i]) {
							transition = node[ns][id0];
							d3_transitionNode(node, i, ns, id1, {
								time: transition.time,
								ease: transition.ease,
								delay: transition.delay + transition.duration,
								duration: transition.duration
							});
						}
						subgroup.push(node);
					}
				}
				return d3_transition(subgroups, ns, id1);
			};

			function d3_transitionNamespace(name) {
				return name == null ? "__transition__" : "__transition_" + name + "__";
			}

			function d3_transitionNode(node, i, ns, id, inherit) {
				var lock = node[ns] || (node[ns] = {
						active: 0,
						count: 0
					}),
					transition = lock[id];
				if(!transition) {
					var time = inherit.time;
					transition = lock[id] = {
						tween: new d3_Map(),
						time: time,
						delay: inherit.delay,
						duration: inherit.duration,
						ease: inherit.ease,
						index: i
					};
					inherit = null;
					++lock.count;
					d3.timer(function(elapsed) {
						var delay = transition.delay,
							duration, ease, timer = d3_timer_active,
							tweened = [];
						timer.t = delay + time;
						if(delay <= elapsed) return start(elapsed - delay);
						timer.c = start;

						function start(elapsed) {
							if(lock.active > id) return stop();
							var active = lock[lock.active];
							if(active) {
								--lock.count;
								delete lock[lock.active];
								active.event && active.event.interrupt.call(node, node.__data__, active.index);
							}
							lock.active = id;
							transition.event && transition.event.start.call(node, node.__data__, i);
							transition.tween.forEach(function(key, value) {
								if(value = value.call(node, node.__data__, i)) {
									tweened.push(value);
								}
							});
							ease = transition.ease;
							duration = transition.duration;
							d3.timer(function() {
								timer.c = tick(elapsed || 1) ? d3_true : tick;
								return 1;
							}, 0, time);
						}

						function tick(elapsed) {
							if(lock.active !== id) return 1;
							var t = elapsed / duration,
								e = ease(t),
								n = tweened.length;
							while(n > 0) {
								tweened[--n].call(node, e);
							}
							if(t >= 1) {
								transition.event && transition.event.end.call(node, node.__data__, i);
								return stop();
							}
						}

						function stop() {
							if(--lock.count) delete lock[id];
							else delete node[ns];
							return 1;
						}
					}, 0, time);
				}
			}
			d3.svg.axis = function() {
				var scale = d3.scale.linear(),
					orient = d3_svg_axisDefaultOrient,
					innerTickSize = 6,
					outerTickSize = 6,
					tickPadding = 3,
					tickArguments_ = [10],
					tickValues = null,
					tickFormat_;

				function axis(g) {
					g.each(function() {
						var g = d3.select(this);
						var scale0 = this.__chart__ || scale,
							scale1 = this.__chart__ = scale.copy();
						var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues,
							tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_,
							tick = g.selectAll(".tick").data(ticks, scale1),
							tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε),
							tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(),
							tickUpdate = d3.transition(tick.order()).style("opacity", 1),
							tickSpacing = Math.max(innerTickSize, 0) + tickPadding,
							tickTransform;
						var range = d3_scaleRange(scale1),
							path = g.selectAll(".domain").data([0]),
							pathUpdate = (path.enter().append("path").attr("class", "domain"),
								d3.transition(path));
						tickEnter.append("line");
						tickEnter.append("text");
						var lineEnter = tickEnter.select("line"),
							lineUpdate = tickUpdate.select("line"),
							text = tick.select("text").text(tickFormat),
							textEnter = tickEnter.select("text"),
							textUpdate = tickUpdate.select("text"),
							sign = orient === "top" || orient === "left" ? -1 : 1,
							x1, x2, y1, y2;
						if(orient === "bottom" || orient === "top") {
							tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2";
							text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle");
							pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize);
						} else {
							tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2";
							text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start");
							pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize);
						}
						lineEnter.attr(y2, sign * innerTickSize);
						textEnter.attr(y1, sign * tickSpacing);
						lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize);
						textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing);
						if(scale1.rangeBand) {
							var x = scale1,
								dx = x.rangeBand() / 2;
							scale0 = scale1 = function(d) {
								return x(d) + dx;
							};
						} else if(scale0.rangeBand) {
							scale0 = scale1;
						} else {
							tickExit.call(tickTransform, scale1, scale0);
						}
						tickEnter.call(tickTransform, scale0, scale1);
						tickUpdate.call(tickTransform, scale1, scale1);
					});
				}
				axis.scale = function(x) {
					if(!arguments.length) return scale;
					scale = x;
					return axis;
				};
				axis.orient = function(x) {
					if(!arguments.length) return orient;
					orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient;
					return axis;
				};
				axis.ticks = function() {
					if(!arguments.length) return tickArguments_;
					tickArguments_ = arguments;
					return axis;
				};
				axis.tickValues = function(x) {
					if(!arguments.length) return tickValues;
					tickValues = x;
					return axis;
				};
				axis.tickFormat = function(x) {
					if(!arguments.length) return tickFormat_;
					tickFormat_ = x;
					return axis;
				};
				axis.tickSize = function(x) {
					var n = arguments.length;
					if(!n) return innerTickSize;
					innerTickSize = +x;
					outerTickSize = +arguments[n - 1];
					return axis;
				};
				axis.innerTickSize = function(x) {
					if(!arguments.length) return innerTickSize;
					innerTickSize = +x;
					return axis;
				};
				axis.outerTickSize = function(x) {
					if(!arguments.length) return outerTickSize;
					outerTickSize = +x;
					return axis;
				};
				axis.tickPadding = function(x) {
					if(!arguments.length) return tickPadding;
					tickPadding = +x;
					return axis;
				};
				axis.tickSubdivide = function() {
					return arguments.length && axis;
				};
				return axis;
			};
			var d3_svg_axisDefaultOrient = "bottom",
				d3_svg_axisOrients = {
					top: 1,
					right: 1,
					bottom: 1,
					left: 1
				};

			function d3_svg_axisX(selection, x0, x1) {
				selection.attr("transform", function(d) {
					var v0 = x0(d);
					return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)";
				});
			}

			function d3_svg_axisY(selection, y0, y1) {
				selection.attr("transform", function(d) {
					var v0 = y0(d);
					return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")";
				});
			}
			d3.svg.brush = function() {
				var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"),
					x = null,
					y = null,
					xExtent = [0, 0],
					yExtent = [0, 0],
					xExtentDomain, yExtentDomain, xClamp = true,
					yClamp = true,
					resizes = d3_svg_brushResizes[0];

				function brush(g) {
					g.each(function() {
						var g = d3.select(this).style("pointer-events", "all").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart);
						var background = g.selectAll(".background").data([0]);
						background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair");
						g.selectAll(".extent").data([0]).enter().append("rect").attr("class", "extent").style("cursor", "move");
						var resize = g.selectAll(".resize").data(resizes, d3_identity);
						resize.exit().remove();
						resize.enter().append("g").attr("class", function(d) {
							return "resize " + d;
						}).style("cursor", function(d) {
							return d3_svg_brushCursor[d];
						}).append("rect").attr("x", function(d) {
							return /[ew]$/.test(d) ? -3 : null;
						}).attr("y", function(d) {
							return /^[ns]/.test(d) ? -3 : null;
						}).attr("width", 6).attr("height", 6).style("visibility", "hidden");
						resize.style("display", brush.empty() ? "none" : null);
						var gUpdate = d3.transition(g),
							backgroundUpdate = d3.transition(background),
							range;
						if(x) {
							range = d3_scaleRange(x);
							backgroundUpdate.attr("x", range[0]).attr("width", range[1] - range[0]);
							redrawX(gUpdate);
						}
						if(y) {
							range = d3_scaleRange(y);
							backgroundUpdate.attr("y", range[0]).attr("height", range[1] - range[0]);
							redrawY(gUpdate);
						}
						redraw(gUpdate);
					});
				}
				brush.event = function(g) {
					g.each(function() {
						var event_ = event.of(this, arguments),
							extent1 = {
								x: xExtent,
								y: yExtent,
								i: xExtentDomain,
								j: yExtentDomain
							},
							extent0 = this.__chart__ || extent1;
						this.__chart__ = extent1;
						if(d3_transitionInheritId) {
							d3.select(this).transition().each("start.brush", function() {
								xExtentDomain = extent0.i;
								yExtentDomain = extent0.j;
								xExtent = extent0.x;
								yExtent = extent0.y;
								event_({
									type: "brushstart"
								});
							}).tween("brush:brush", function() {
								var xi = d3_interpolateArray(xExtent, extent1.x),
									yi = d3_interpolateArray(yExtent, extent1.y);
								xExtentDomain = yExtentDomain = null;
								return function(t) {
									xExtent = extent1.x = xi(t);
									yExtent = extent1.y = yi(t);
									event_({
										type: "brush",
										mode: "resize"
									});
								};
							}).each("end.brush", function() {
								xExtentDomain = extent1.i;
								yExtentDomain = extent1.j;
								event_({
									type: "brush",
									mode: "resize"
								});
								event_({
									type: "brushend"
								});
							});
						} else {
							event_({
								type: "brushstart"
							});
							event_({
								type: "brush",
								mode: "resize"
							});
							event_({
								type: "brushend"
							});
						}
					});
				};

				function redraw(g) {
					g.selectAll(".resize").attr("transform", function(d) {
						return "translate(" + xExtent[+/e$/.test(d)] + "," + yExtent[+/^s/.test(d)] + ")";
					});
				}

				function redrawX(g) {
					g.select(".extent").attr("x", xExtent[0]);
					g.selectAll(".extent,.n>rect,.s>rect").attr("width", xExtent[1] - xExtent[0]);
				}

				function redrawY(g) {
					g.select(".extent").attr("y", yExtent[0]);
					g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]);
				}

				function brushstart() {
					var target = this,
						eventTarget = d3.select(d3.event.target),
						event_ = event.of(target, arguments),
						g = d3.select(target),
						resizing = eventTarget.datum(),
						resizingX = !/^(n|s)$/.test(resizing) && x,
						resizingY = !/^(e|w)$/.test(resizing) && y,
						dragging = eventTarget.classed("extent"),
						dragRestore = d3_event_dragSuppress(target),
						center, origin = d3.mouse(target),
						offset;
					var w = d3.select(d3_window(target)).on("keydown.brush", keydown).on("keyup.brush", keyup);
					if(d3.event.changedTouches) {
						w.on("touchmove.brush", brushmove).on("touchend.brush", brushend);
					} else {
						w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend);
					}
					g.interrupt().selectAll("*").interrupt();
					if(dragging) {
						origin[0] = xExtent[0] - origin[0];
						origin[1] = yExtent[0] - origin[1];
					} else if(resizing) {
						var ex = +/w$/.test(resizing),
							ey = +/^n/.test(resizing);
						offset = [xExtent[1 - ex] - origin[0], yExtent[1 - ey] - origin[1]];
						origin[0] = xExtent[ex];
						origin[1] = yExtent[ey];
					} else if(d3.event.altKey) center = origin.slice();
					g.style("pointer-events", "none").selectAll(".resize").style("display", null);
					d3.select("body").style("cursor", eventTarget.style("cursor"));
					event_({
						type: "brushstart"
					});
					brushmove();

					function keydown() {
						if(d3.event.keyCode == 32) {
							if(!dragging) {
								center = null;
								origin[0] -= xExtent[1];
								origin[1] -= yExtent[1];
								dragging = 2;
							}
							d3_eventPreventDefault();
						}
					}

					function keyup() {
						if(d3.event.keyCode == 32 && dragging == 2) {
							origin[0] += xExtent[1];
							origin[1] += yExtent[1];
							dragging = 0;
							d3_eventPreventDefault();
						}
					}

					function brushmove() {
						var point = d3.mouse(target),
							moved = false;
						if(offset) {
							point[0] += offset[0];
							point[1] += offset[1];
						}
						if(!dragging) {
							if(d3.event.altKey) {
								if(!center) center = [(xExtent[0] + xExtent[1]) / 2, (yExtent[0] + yExtent[1]) / 2];
								origin[0] = xExtent[+(point[0] < center[0])];
								origin[1] = yExtent[+(point[1] < center[1])];
							} else center = null;
						}
						if(resizingX && move1(point, x, 0)) {
							redrawX(g);
							moved = true;
						}
						if(resizingY && move1(point, y, 1)) {
							redrawY(g);
							moved = true;
						}
						if(moved) {
							redraw(g);
							event_({
								type: "brush",
								mode: dragging ? "move" : "resize"
							});
						}
					}

					function move1(point, scale, i) {
						var range = d3_scaleRange(scale),
							r0 = range[0],
							r1 = range[1],
							position = origin[i],
							extent = i ? yExtent : xExtent,
							size = extent[1] - extent[0],
							min, max;
						if(dragging) {
							r0 -= position;
							r1 -= size + position;
						}
						min = (i ? yClamp : xClamp) ? Math.max(r0, Math.min(r1, point[i])) : point[i];
						if(dragging) {
							max = (min += position) + size;
						} else {
							if(center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min));
							if(position < min) {
								max = min;
								min = position;
							} else {
								max = position;
							}
						}
						if(extent[0] != min || extent[1] != max) {
							if(i) yExtentDomain = null;
							else xExtentDomain = null;
							extent[0] = min;
							extent[1] = max;
							return true;
						}
					}

					function brushend() {
						brushmove();
						g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null);
						d3.select("body").style("cursor", null);
						w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null);
						dragRestore();
						event_({
							type: "brushend"
						});
					}
				}
				brush.x = function(z) {
					if(!arguments.length) return x;
					x = z;
					resizes = d3_svg_brushResizes[!x << 1 | !y];
					return brush;
				};
				brush.y = function(z) {
					if(!arguments.length) return y;
					y = z;
					resizes = d3_svg_brushResizes[!x << 1 | !y];
					return brush;
				};
				brush.clamp = function(z) {
					if(!arguments.length) return x && y ? [xClamp, yClamp] : x ? xClamp : y ? yClamp : null;
					if(x && y) xClamp = !!z[0], yClamp = !!z[1];
					else if(x) xClamp = !!z;
					else if(y) yClamp = !!z;
					return brush;
				};
				brush.extent = function(z) {
					var x0, x1, y0, y1, t;
					if(!arguments.length) {
						if(x) {
							if(xExtentDomain) {
								x0 = xExtentDomain[0], x1 = xExtentDomain[1];
							} else {
								x0 = xExtent[0], x1 = xExtent[1];
								if(x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
								if(x1 < x0) t = x0, x0 = x1, x1 = t;
							}
						}
						if(y) {
							if(yExtentDomain) {
								y0 = yExtentDomain[0], y1 = yExtentDomain[1];
							} else {
								y0 = yExtent[0], y1 = yExtent[1];
								if(y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
								if(y1 < y0) t = y0, y0 = y1, y1 = t;
							}
						}
						return x && y ? [
							[x0, y0],
							[x1, y1]
						] : x ? [x0, x1] : y && [y0, y1];
					}
					if(x) {
						x0 = z[0], x1 = z[1];
						if(y) x0 = x0[0], x1 = x1[0];
						xExtentDomain = [x0, x1];
						if(x.invert) x0 = x(x0), x1 = x(x1);
						if(x1 < x0) t = x0, x0 = x1, x1 = t;
						if(x0 != xExtent[0] || x1 != xExtent[1]) xExtent = [x0, x1];
					}
					if(y) {
						y0 = z[0], y1 = z[1];
						if(x) y0 = y0[1], y1 = y1[1];
						yExtentDomain = [y0, y1];
						if(y.invert) y0 = y(y0), y1 = y(y1);
						if(y1 < y0) t = y0, y0 = y1, y1 = t;
						if(y0 != yExtent[0] || y1 != yExtent[1]) yExtent = [y0, y1];
					}
					return brush;
				};
				brush.clear = function() {
					if(!brush.empty()) {
						xExtent = [0, 0], yExtent = [0, 0];
						xExtentDomain = yExtentDomain = null;
					}
					return brush;
				};
				brush.empty = function() {
					return !!x && xExtent[0] == xExtent[1] || !!y && yExtent[0] == yExtent[1];
				};
				return d3.rebind(brush, event, "on");
			};
			var d3_svg_brushCursor = {
				n: "ns-resize",
				e: "ew-resize",
				s: "ns-resize",
				w: "ew-resize",
				nw: "nwse-resize",
				ne: "nesw-resize",
				se: "nwse-resize",
				sw: "nesw-resize"
			};
			var d3_svg_brushResizes = [
				["n", "e", "s", "w", "nw", "ne", "se", "sw"],
				["e", "w"],
				["n", "s"],
				[]
			];
			var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat;
			var d3_time_formatUtc = d3_time_format.utc;
			var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ");
			d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso;

			function d3_time_formatIsoNative(date) {
				return date.toISOString();
			}
			d3_time_formatIsoNative.parse = function(string) {
				var date = new Date(string);
				return isNaN(date) ? null : date;
			};
			d3_time_formatIsoNative.toString = d3_time_formatIso.toString;
			d3_time.second = d3_time_interval(function(date) {
				return new d3_date(Math.floor(date / 1e3) * 1e3);
			}, function(date, offset) {
				date.setTime(date.getTime() + Math.floor(offset) * 1e3);
			}, function(date) {
				return date.getSeconds();
			});
			d3_time.seconds = d3_time.second.range;
			d3_time.seconds.utc = d3_time.second.utc.range;
			d3_time.minute = d3_time_interval(function(date) {
				return new d3_date(Math.floor(date / 6e4) * 6e4);
			}, function(date, offset) {
				date.setTime(date.getTime() + Math.floor(offset) * 6e4);
			}, function(date) {
				return date.getMinutes();
			});
			d3_time.minutes = d3_time.minute.range;
			d3_time.minutes.utc = d3_time.minute.utc.range;
			d3_time.hour = d3_time_interval(function(date) {
				var timezone = date.getTimezoneOffset() / 60;
				return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5);
			}, function(date, offset) {
				date.setTime(date.getTime() + Math.floor(offset) * 36e5);
			}, function(date) {
				return date.getHours();
			});
			d3_time.hours = d3_time.hour.range;
			d3_time.hours.utc = d3_time.hour.utc.range;
			d3_time.month = d3_time_interval(function(date) {
				date = d3_time.day(date);
				date.setDate(1);
				return date;
			}, function(date, offset) {
				date.setMonth(date.getMonth() + offset);
			}, function(date) {
				return date.getMonth();
			});
			d3_time.months = d3_time.month.range;
			d3_time.months.utc = d3_time.month.utc.range;

			function d3_time_scale(linear, methods, format) {
				function scale(x) {
					return linear(x);
				}
				scale.invert = function(x) {
					return d3_time_scaleDate(linear.invert(x));
				};
				scale.domain = function(x) {
					if(!arguments.length) return linear.domain().map(d3_time_scaleDate);
					linear.domain(x);
					return scale;
				};

				function tickMethod(extent, count) {
					var span = extent[1] - extent[0],
						target = span / count,
						i = d3.bisect(d3_time_scaleSteps, target);
					return i == d3_time_scaleSteps.length ? [methods.year, d3_scale_linearTickRange(extent.map(function(d) {
						return d / 31536e6;
					}), count)[2]] : !i ? [d3_time_scaleMilliseconds, d3_scale_linearTickRange(extent, count)[2]] : methods[target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target ? i - 1 : i];
				}
				scale.nice = function(interval, skip) {
					var domain = scale.domain(),
						extent = d3_scaleExtent(domain),
						method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" && tickMethod(extent, interval);
					if(method) interval = method[0], skip = method[1];

					function skipped(date) {
						return !isNaN(date) && !interval.range(date, d3_time_scaleDate(+date + 1), skip).length;
					}
					return scale.domain(d3_scale_nice(domain, skip > 1 ? {
						floor: function(date) {
							while(skipped(date = interval.floor(date))) date = d3_time_scaleDate(date - 1);
							return date;
						},
						ceil: function(date) {
							while(skipped(date = interval.ceil(date))) date = d3_time_scaleDate(+date + 1);
							return date;
						}
					} : interval));
				};
				scale.ticks = function(interval, skip) {
					var extent = d3_scaleExtent(scale.domain()),
						method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [{
							range: interval
						}, skip];
					if(method) interval = method[0], skip = method[1];
					return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip);
				};
				scale.tickFormat = function() {
					return format;
				};
				scale.copy = function() {
					return d3_time_scale(linear.copy(), methods, format);
				};
				return d3_scale_linearRebind(scale, linear);
			}

			function d3_time_scaleDate(t) {
				return new Date(t);
			}
			var d3_time_scaleSteps = [1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6];
			var d3_time_scaleLocalMethods = [
				[d3_time.second, 1],
				[d3_time.second, 5],
				[d3_time.second, 15],
				[d3_time.second, 30],
				[d3_time.minute, 1],
				[d3_time.minute, 5],
				[d3_time.minute, 15],
				[d3_time.minute, 30],
				[d3_time.hour, 1],
				[d3_time.hour, 3],
				[d3_time.hour, 6],
				[d3_time.hour, 12],
				[d3_time.day, 1],
				[d3_time.day, 2],
				[d3_time.week, 1],
				[d3_time.month, 1],
				[d3_time.month, 3],
				[d3_time.year, 1]
			];
			var d3_time_scaleLocalFormat = d3_time_format.multi([
				[".%L", function(d) {
					return d.getMilliseconds();
				}],
				[":%S", function(d) {
					return d.getSeconds();
				}],
				["%I:%M", function(d) {
					return d.getMinutes();
				}],
				["%I %p", function(d) {
					return d.getHours();
				}],
				["%a %d", function(d) {
					return d.getDay() && d.getDate() != 1;
				}],
				["%b %d", function(d) {
					return d.getDate() != 1;
				}],
				["%B", function(d) {
					return d.getMonth();
				}],
				["%Y", d3_true]
			]);
			var d3_time_scaleMilliseconds = {
				range: function(start, stop, step) {
					return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate);
				},
				floor: d3_identity,
				ceil: d3_identity
			};
			d3_time_scaleLocalMethods.year = d3_time.year;
			d3_time.scale = function() {
				return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat);
			};
			var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) {
				return [m[0].utc, m[1]];
			});
			var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([
				[".%L", function(d) {
					return d.getUTCMilliseconds();
				}],
				[":%S", function(d) {
					return d.getUTCSeconds();
				}],
				["%I:%M", function(d) {
					return d.getUTCMinutes();
				}],
				["%I %p", function(d) {
					return d.getUTCHours();
				}],
				["%a %d", function(d) {
					return d.getUTCDay() && d.getUTCDate() != 1;
				}],
				["%b %d", function(d) {
					return d.getUTCDate() != 1;
				}],
				["%B", function(d) {
					return d.getUTCMonth();
				}],
				["%Y", d3_true]
			]);
			d3_time_scaleUtcMethods.year = d3_time.year.utc;
			d3_time.scale.utc = function() {
				return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat);
			};
			d3.text = d3_xhrType(function(request) {
				return request.responseText;
			});
			d3.json = function(url, callback) {
				return d3_xhr(url, "application/json", d3_json, callback);
			};

			function d3_json(request) {
				return JSON.parse(request.responseText);
			}
			d3.html = function(url, callback) {
				return d3_xhr(url, "text/html", d3_html, callback);
			};

			function d3_html(request) {
				var range = d3_document.createRange();
				range.selectNode(d3_document.body);
				return range.createContextualFragment(request.responseText);
			}
			d3.xml = d3_xhrType(function(request) {
				return request.responseXML;
			});
			if(typeof define === "function" && define.amd) define(d3);
			else if(typeof module === "object" && module.exports) module.exports = d3;
			this.d3 = d3;
		}();
	}, {}],
	4: [function(require, module, exports) {
		! function() {
			var topojson = {
				version: "1.6.19",
				mesh: function(topology) {
					return object(topology, meshArcs.apply(this, arguments));
				},
				meshArcs: meshArcs,
				merge: function(topology) {
					return object(topology, mergeArcs.apply(this, arguments));
				},
				mergeArcs: mergeArcs,
				feature: featureOrCollection,
				neighbors: neighbors,
				presimplify: presimplify
			};

			function stitchArcs(topology, arcs) {
				var stitchedArcs = {},
					fragmentByStart = {},
					fragmentByEnd = {},
					fragments = [],
					emptyIndex = -1;

				// Stitch empty arcs first, since they may be subsumed by other arcs.
				arcs.forEach(function(i, j) {
					var arc = topology.arcs[i < 0 ? ~i : i],
						t;
					if(arc.length < 3 && !arc[1][0] && !arc[1][1]) {
						t = arcs[++emptyIndex], arcs[emptyIndex] = i, arcs[j] = t;
					}
				});

				arcs.forEach(function(i) {
					var e = ends(i),
						start = e[0],
						end = e[1],
						f, g;

					if(f = fragmentByEnd[start]) {
						delete fragmentByEnd[f.end];
						f.push(i);
						f.end = end;
						if(g = fragmentByStart[end]) {
							delete fragmentByStart[g.start];
							var fg = g === f ? f : f.concat(g);
							fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg;
						} else {
							fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
						}
					} else if(f = fragmentByStart[end]) {
						delete fragmentByStart[f.start];
						f.unshift(i);
						f.start = start;
						if(g = fragmentByEnd[start]) {
							delete fragmentByEnd[g.end];
							var gf = g === f ? f : g.concat(f);
							fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf;
						} else {
							fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
						}
					} else {
						f = [i];
						fragmentByStart[f.start = start] = fragmentByEnd[f.end = end] = f;
					}
				});

				function ends(i) {
					var arc = topology.arcs[i < 0 ? ~i : i],
						p0 = arc[0],
						p1;
					if(topology.transform) p1 = [0, 0], arc.forEach(function(dp) {
						p1[0] += dp[0], p1[1] += dp[1];
					});
					else p1 = arc[arc.length - 1];
					return i < 0 ? [p1, p0] : [p0, p1];
				}

				function flush(fragmentByEnd, fragmentByStart) {
					for(var k in fragmentByEnd) {
						var f = fragmentByEnd[k];
						delete fragmentByStart[f.start];
						delete f.start;
						delete f.end;
						f.forEach(function(i) {
							stitchedArcs[i < 0 ? ~i : i] = 1;
						});
						fragments.push(f);
					}
				}

				flush(fragmentByEnd, fragmentByStart);
				flush(fragmentByStart, fragmentByEnd);
				arcs.forEach(function(i) {
					if(!stitchedArcs[i < 0 ? ~i : i]) fragments.push([i]);
				});

				return fragments;
			}

			function meshArcs(topology, o, filter) {
				var arcs = [];

				if(arguments.length > 1) {
					var geomsByArc = [],
						geom;

					function arc(i) {
						var j = i < 0 ? ~i : i;
						(geomsByArc[j] || (geomsByArc[j] = [])).push({
							i: i,
							g: geom
						});
					}

					function line(arcs) {
						arcs.forEach(arc);
					}

					function polygon(arcs) {
						arcs.forEach(line);
					}

					function geometry(o) {
						if(o.type === "GeometryCollection") o.geometries.forEach(geometry);
						else if(o.type in geometryType) geom = o, geometryType[o.type](o.arcs);
					}

					var geometryType = {
						LineString: line,
						MultiLineString: polygon,
						Polygon: polygon,
						MultiPolygon: function(arcs) {
							arcs.forEach(polygon);
						}
					};

					geometry(o);

					geomsByArc.forEach(arguments.length < 3 ?

						function(geoms) {
							arcs.push(geoms[0].i);
						} :
						function(geoms) {
							if(filter(geoms[0].g, geoms[geoms.length - 1].g)) arcs.push(geoms[0].i);
						});
				} else {
					for(var i = 0, n = topology.arcs.length; i < n; ++i) arcs.push(i);
				}

				return {
					type: "MultiLineString",
					arcs: stitchArcs(topology, arcs)
				};
			}

			function mergeArcs(topology, objects) {
				var polygonsByArc = {},
					polygons = [],
					components = [];

				objects.forEach(function(o) {
					if(o.type === "Polygon") register(o.arcs);
					else if(o.type === "MultiPolygon") o.arcs.forEach(register);
				});

				function register(polygon) {
					polygon.forEach(function(ring) {
						ring.forEach(function(arc) {
							(polygonsByArc[arc = arc < 0 ? ~arc : arc] || (polygonsByArc[arc] = [])).push(polygon);
						});
					});
					polygons.push(polygon);
				}

				function exterior(ring) {
					return cartesianRingArea(object(topology, {
						type: "Polygon",
						arcs: [ring]
					}).coordinates[0]) > 0; // TODO allow spherical?
				}

				polygons.forEach(function(polygon) {
					if(!polygon._) {
						var component = [],
							neighbors = [polygon];
						polygon._ = 1;
						components.push(component);
						while(polygon = neighbors.pop()) {
							component.push(polygon);
							polygon.forEach(function(ring) {
								ring.forEach(function(arc) {
									polygonsByArc[arc < 0 ? ~arc : arc].forEach(function(polygon) {
										if(!polygon._) {
											polygon._ = 1;
											neighbors.push(polygon);
										}
									});
								});
							});
						}
					}
				});

				polygons.forEach(function(polygon) {
					delete polygon._;
				});

				return {
					type: "MultiPolygon",
					arcs: components.map(function(polygons) {
						var arcs = [];

						// Extract the exterior (unique) arcs.
						polygons.forEach(function(polygon) {
							polygon.forEach(function(ring) {
								ring.forEach(function(arc) {
									if(polygonsByArc[arc < 0 ? ~arc : arc].length < 2) {
										arcs.push(arc);
									}
								});
							});
						});

						// Stitch the arcs into one or more rings.
						arcs = stitchArcs(topology, arcs);

						// If more than one ring is returned,
						// at most one of these rings can be the exterior;
						// this exterior ring has the same winding order
						// as any exterior ring in the original polygons.
						if((n = arcs.length) > 1) {
							var sgn = exterior(polygons[0][0]);
							for(var i = 0, t; i < n; ++i) {
								if(sgn === exterior(arcs[i])) {
									t = arcs[0], arcs[0] = arcs[i], arcs[i] = t;
									break;
								}
							}
						}

						return arcs;
					})
				};
			}

			function featureOrCollection(topology, o) {
				return o.type === "GeometryCollection" ? {
					type: "FeatureCollection",
					features: o.geometries.map(function(o) {
						return feature(topology, o);
					})
				} : feature(topology, o);
			}

			function feature(topology, o) {
				var f = {
					type: "Feature",
					id: o.id,
					properties: o.properties || {},
					geometry: object(topology, o)
				};
				if(o.id == null) delete f.id;
				return f;
			}

			function object(topology, o) {
				var absolute = transformAbsolute(topology.transform),
					arcs = topology.arcs;

				function arc(i, points) {
					if(points.length) points.pop();
					for(var a = arcs[i < 0 ? ~i : i], k = 0, n = a.length, p; k < n; ++k) {
						points.push(p = a[k].slice());
						absolute(p, k);
					}
					if(i < 0) reverse(points, n);
				}

				function point(p) {
					p = p.slice();
					absolute(p, 0);
					return p;
				}

				function line(arcs) {
					var points = [];
					for(var i = 0, n = arcs.length; i < n; ++i) arc(arcs[i], points);
					if(points.length < 2) points.push(points[0].slice());
					return points;
				}

				function ring(arcs) {
					var points = line(arcs);
					while(points.length < 4) points.push(points[0].slice());
					return points;
				}

				function polygon(arcs) {
					return arcs.map(ring);
				}

				function geometry(o) {
					var t = o.type;
					return t === "GeometryCollection" ? {
							type: t,
							geometries: o.geometries.map(geometry)
						} :
						t in geometryType ? {
							type: t,
							coordinates: geometryType[t](o)
						} :
						null;
				}

				var geometryType = {
					Point: function(o) {
						return point(o.coordinates);
					},
					MultiPoint: function(o) {
						return o.coordinates.map(point);
					},
					LineString: function(o) {
						return line(o.arcs);
					},
					MultiLineString: function(o) {
						return o.arcs.map(line);
					},
					Polygon: function(o) {
						return polygon(o.arcs);
					},
					MultiPolygon: function(o) {
						return o.arcs.map(polygon);
					}
				};

				return geometry(o);
			}

			function reverse(array, n) {
				var t, j = array.length,
					i = j - n;
				while(i < --j) t = array[i], array[i++] = array[j], array[j] = t;
			}

			function bisect(a, x) {
				var lo = 0,
					hi = a.length;
				while(lo < hi) {
					var mid = lo + hi >>> 1;
					if(a[mid] < x) lo = mid + 1;
					else hi = mid;
				}
				return lo;
			}

			function neighbors(objects) {
				var indexesByArc = {}, // arc index -> array of object indexes
					neighbors = objects.map(function() {
						return [];
					});

				function line(arcs, i) {
					arcs.forEach(function(a) {
						if(a < 0) a = ~a;
						var o = indexesByArc[a];
						if(o) o.push(i);
						else indexesByArc[a] = [i];
					});
				}

				function polygon(arcs, i) {
					arcs.forEach(function(arc) {
						line(arc, i);
					});
				}

				function geometry(o, i) {
					if(o.type === "GeometryCollection") o.geometries.forEach(function(o) {
						geometry(o, i);
					});
					else if(o.type in geometryType) geometryType[o.type](o.arcs, i);
				}

				var geometryType = {
					LineString: line,
					MultiLineString: polygon,
					Polygon: polygon,
					MultiPolygon: function(arcs, i) {
						arcs.forEach(function(arc) {
							polygon(arc, i);
						});
					}
				};

				objects.forEach(geometry);

				for(var i in indexesByArc) {
					for(var indexes = indexesByArc[i], m = indexes.length, j = 0; j < m; ++j) {
						for(var k = j + 1; k < m; ++k) {
							var ij = indexes[j],
								ik = indexes[k],
								n;
							if((n = neighbors[ij])[i = bisect(n, ik)] !== ik) n.splice(i, 0, ik);
							if((n = neighbors[ik])[i = bisect(n, ij)] !== ij) n.splice(i, 0, ij);
						}
					}
				}

				return neighbors;
			}

			function presimplify(topology, triangleArea) {
				var absolute = transformAbsolute(topology.transform),
					relative = transformRelative(topology.transform),
					heap = minAreaHeap();

				if(!triangleArea) triangleArea = cartesianTriangleArea;

				topology.arcs.forEach(function(arc) {
					var triangles = [],
						maxArea = 0,
						triangle;

					// To store each point’s effective area, we create a new array rather than
					// extending the passed-in point to workaround a Chrome/V8 bug (getting
					// stuck in smi mode). For midpoints, the initial effective area of
					// Infinity will be computed in the next step.
					for(var i = 0, n = arc.length, p; i < n; ++i) {
						p = arc[i];
						absolute(arc[i] = [p[0], p[1], Infinity], i);
					}

					for(var i = 1, n = arc.length - 1; i < n; ++i) {
						triangle = arc.slice(i - 1, i + 2);
						triangle[1][2] = triangleArea(triangle);
						triangles.push(triangle);
						heap.push(triangle);
					}

					for(var i = 0, n = triangles.length; i < n; ++i) {
						triangle = triangles[i];
						triangle.previous = triangles[i - 1];
						triangle.next = triangles[i + 1];
					}

					while(triangle = heap.pop()) {
						var previous = triangle.previous,
							next = triangle.next;

						// If the area of the current point is less than that of the previous point
						// to be eliminated, use the latter's area instead. This ensures that the
						// current point cannot be eliminated without eliminating previously-
						// eliminated points.
						if(triangle[1][2] < maxArea) triangle[1][2] = maxArea;
						else maxArea = triangle[1][2];

						if(previous) {
							previous.next = next;
							previous[2] = triangle[2];
							update(previous);
						}

						if(next) {
							next.previous = previous;
							next[0] = triangle[0];
							update(next);
						}
					}

					arc.forEach(relative);
				});

				function update(triangle) {
					heap.remove(triangle);
					triangle[1][2] = triangleArea(triangle);
					heap.push(triangle);
				}

				return topology;
			};

			function cartesianRingArea(ring) {
				var i = -1,
					n = ring.length,
					a,
					b = ring[n - 1],
					area = 0;

				while(++i < n) {
					a = b;
					b = ring[i];
					area += a[0] * b[1] - a[1] * b[0];
				}

				return area * .5;
			}

			function cartesianTriangleArea(triangle) {
				var a = triangle[0],
					b = triangle[1],
					c = triangle[2];
				return Math.abs((a[0] - c[0]) * (b[1] - a[1]) - (a[0] - b[0]) * (c[1] - a[1]));
			}

			function compareArea(a, b) {
				return a[1][2] - b[1][2];
			}

			function minAreaHeap() {
				var heap = {},
					array = [],
					size = 0;

				heap.push = function(object) {
					up(array[object._ = size] = object, size++);
					return size;
				};

				heap.pop = function() {
					if(size <= 0) return;
					var removed = array[0],
						object;
					if(--size > 0) object = array[size], down(array[object._ = 0] = object, 0);
					return removed;
				};

				heap.remove = function(removed) {
					var i = removed._,
						object;
					if(array[i] !== removed) return; // invalid request
					if(i !== --size) object = array[size], (compareArea(object, removed) < 0 ? up : down)(array[object._ = i] = object, i);
					return i;
				};

				function up(object, i) {
					while(i > 0) {
						var j = ((i + 1) >> 1) - 1,
							parent = array[j];
						if(compareArea(object, parent) >= 0) break;
						array[parent._ = i] = parent;
						array[object._ = i = j] = object;
					}
				}

				function down(object, i) {
					while(true) {
						var r = (i + 1) << 1,
							l = r - 1,
							j = i,
							child = array[j];
						if(l < size && compareArea(array[l], child) < 0) child = array[j = l];
						if(r < size && compareArea(array[r], child) < 0) child = array[j = r];
						if(j === i) break;
						array[child._ = i] = child;
						array[object._ = i = j] = object;
					}
				}

				return heap;
			}

			function transformAbsolute(transform) {
				if(!transform) return noop;
				var x0,
					y0,
					kx = transform.scale[0],
					ky = transform.scale[1],
					dx = transform.translate[0],
					dy = transform.translate[1];
				return function(point, i) {
					if(!i) x0 = y0 = 0;
					point[0] = (x0 += point[0]) * kx + dx;
					point[1] = (y0 += point[1]) * ky + dy;
				};
			}

			function transformRelative(transform) {
				if(!transform) return noop;
				var x0,
					y0,
					kx = transform.scale[0],
					ky = transform.scale[1],
					dx = transform.translate[0],
					dy = transform.translate[1];
				return function(point, i) {
					if(!i) x0 = y0 = 0;
					var x1 = (point[0] - dx) / kx | 0,
						y1 = (point[1] - dy) / ky | 0;
					point[0] = x1 - x0;
					point[1] = y1 - y0;
					x0 = x1;
					y0 = y1;
				};
			}

			function noop() {}

			if(typeof define === "function" && define.amd) define(topojson);
			else if(typeof module === "object" && module.exports) module.exports = topojson;
			else this.topojson = topojson;
		}();

	}, {}],
	5: [function(require, module, exports) {
		//     Underscore.js 1.8.3
		//     http://underscorejs.org
		//     (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
		//     Underscore may be freely distributed under the MIT license.

		(function() {

			// Baseline setup
			// --------------

			// Establish the root object, `window` in the browser, or `exports` on the server.
			var root = this;

			// Save the previous value of the `_` variable.
			var previousUnderscore = root._;

			// Save bytes in the minified (but not gzipped) version:
			var ArrayProto = Array.prototype,
				ObjProto = Object.prototype,
				FuncProto = Function.prototype;

			// Create quick reference variables for speed access to core prototypes.
			var
				push = ArrayProto.push,
				slice = ArrayProto.slice,
				toString = ObjProto.toString,
				hasOwnProperty = ObjProto.hasOwnProperty;

			// All **ECMAScript 5** native function implementations that we hope to use
			// are declared here.
			var
				nativeIsArray = Array.isArray,
				nativeKeys = Object.keys,
				nativeBind = FuncProto.bind,
				nativeCreate = Object.create;

			// Naked function reference for surrogate-prototype-swapping.
			var Ctor = function() {};

			// Create a safe reference to the Underscore object for use below.
			var _ = function(obj) {
				if(obj instanceof _) return obj;
				if(!(this instanceof _)) return new _(obj);
				this._wrapped = obj;
			};

			// Export the Underscore object for **Node.js**, with
			// backwards-compatibility for the old `require()` API. If we're in
			// the browser, add `_` as a global object.
			if(typeof exports !== 'undefined') {
				if(typeof module !== 'undefined' && module.exports) {
					exports = module.exports = _;
				}
				exports._ = _;
			} else {
				root._ = _;
			}

			// Current version.
			_.VERSION = '1.8.3';

			// Internal function that returns an efficient (for current engines) version
			// of the passed-in callback, to be repeatedly applied in other Underscore
			// functions.
			var optimizeCb = function(func, context, argCount) {
				if(context === void 0) return func;
				switch(argCount == null ? 3 : argCount) {
					case 1:
						return function(value) {
							return func.call(context, value);
						};
					case 2:
						return function(value, other) {
							return func.call(context, value, other);
						};
					case 3:
						return function(value, index, collection) {
							return func.call(context, value, index, collection);
						};
					case 4:
						return function(accumulator, value, index, collection) {
							return func.call(context, accumulator, value, index, collection);
						};
				}
				return function() {
					return func.apply(context, arguments);
				};
			};

			// A mostly-internal function to generate callbacks that can be applied
			// to each element in a collection, returning the desired result — either
			// identity, an arbitrary callback, a property matcher, or a property accessor.
			var cb = function(value, context, argCount) {
				if(value == null) return _.identity;
				if(_.isFunction(value)) return optimizeCb(value, context, argCount);
				if(_.isObject(value)) return _.matcher(value);
				return _.property(value);
			};
			_.iteratee = function(value, context) {
				return cb(value, context, Infinity);
			};

			// An internal function for creating assigner functions.
			var createAssigner = function(keysFunc, undefinedOnly) {
				return function(obj) {
					var length = arguments.length;
					if(length < 2 || obj == null) return obj;
					for(var index = 1; index < length; index++) {
						var source = arguments[index],
							keys = keysFunc(source),
							l = keys.length;
						for(var i = 0; i < l; i++) {
							var key = keys[i];
							if(!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
						}
					}
					return obj;
				};
			};

			// An internal function for creating a new object that inherits from another.
			var baseCreate = function(prototype) {
				if(!_.isObject(prototype)) return {};
				if(nativeCreate) return nativeCreate(prototype);
				Ctor.prototype = prototype;
				var result = new Ctor;
				Ctor.prototype = null;
				return result;
			};

			var property = function(key) {
				return function(obj) {
					return obj == null ? void 0 : obj[key];
				};
			};

			// Helper for collection methods to determine whether a collection
			// should be iterated as an array or as an object
			// Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
			// Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
			var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
			var getLength = property('length');
			var isArrayLike = function(collection) {
				var length = getLength(collection);
				return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
			};

			// Collection Functions
			// --------------------

			// The cornerstone, an `each` implementation, aka `forEach`.
			// Handles raw objects in addition to array-likes. Treats all
			// sparse array-likes as if they were dense.
			_.each = _.forEach = function(obj, iteratee, context) {
				iteratee = optimizeCb(iteratee, context);
				var i, length;
				if(isArrayLike(obj)) {
					for(i = 0, length = obj.length; i < length; i++) {
						iteratee(obj[i], i, obj);
					}
				} else {
					var keys = _.keys(obj);
					for(i = 0, length = keys.length; i < length; i++) {
						iteratee(obj[keys[i]], keys[i], obj);
					}
				}
				return obj;
			};

			// Return the results of applying the iteratee to each element.
			_.map = _.collect = function(obj, iteratee, context) {
				iteratee = cb(iteratee, context);
				var keys = !isArrayLike(obj) && _.keys(obj),
					length = (keys || obj).length,
					results = Array(length);
				for(var index = 0; index < length; index++) {
					var currentKey = keys ? keys[index] : index;
					results[index] = iteratee(obj[currentKey], currentKey, obj);
				}
				return results;
			};

			// Create a reducing function iterating left or right.
			function createReduce(dir) {
				// Optimized iterator function as using arguments.length
				// in the main function will deoptimize the, see #1991.
				function iterator(obj, iteratee, memo, keys, index, length) {
					for(; index >= 0 && index < length; index += dir) {
						var currentKey = keys ? keys[index] : index;
						memo = iteratee(memo, obj[currentKey], currentKey, obj);
					}
					return memo;
				}

				return function(obj, iteratee, memo, context) {
					iteratee = optimizeCb(iteratee, context, 4);
					var keys = !isArrayLike(obj) && _.keys(obj),
						length = (keys || obj).length,
						index = dir > 0 ? 0 : length - 1;
					// Determine the initial value if none is provided.
					if(arguments.length < 3) {
						memo = obj[keys ? keys[index] : index];
						index += dir;
					}
					return iterator(obj, iteratee, memo, keys, index, length);
				};
			}

			// **Reduce** builds up a single result from a list of values, aka `inject`,
			// or `foldl`.
			_.reduce = _.foldl = _.inject = createReduce(1);

			// The right-associative version of reduce, also known as `foldr`.
			_.reduceRight = _.foldr = createReduce(-1);

			// Return the first value which passes a truth test. Aliased as `detect`.
			_.find = _.detect = function(obj, predicate, context) {
				var key;
				if(isArrayLike(obj)) {
					key = _.findIndex(obj, predicate, context);
				} else {
					key = _.findKey(obj, predicate, context);
				}
				if(key !== void 0 && key !== -1) return obj[key];
			};

			// Return all the elements that pass a truth test.
			// Aliased as `select`.
			_.filter = _.select = function(obj, predicate, context) {
				var results = [];
				predicate = cb(predicate, context);
				_.each(obj, function(value, index, list) {
					if(predicate(value, index, list)) results.push(value);
				});
				return results;
			};

			// Return all the elements for which a truth test fails.
			_.reject = function(obj, predicate, context) {
				return _.filter(obj, _.negate(cb(predicate)), context);
			};

			// Determine whether all of the elements match a truth test.
			// Aliased as `all`.
			_.every = _.all = function(obj, predicate, context) {
				predicate = cb(predicate, context);
				var keys = !isArrayLike(obj) && _.keys(obj),
					length = (keys || obj).length;
				for(var index = 0; index < length; index++) {
					var currentKey = keys ? keys[index] : index;
					if(!predicate(obj[currentKey], currentKey, obj)) return false;
				}
				return true;
			};

			// Determine if at least one element in the object matches a truth test.
			// Aliased as `any`.
			_.some = _.any = function(obj, predicate, context) {
				predicate = cb(predicate, context);
				var keys = !isArrayLike(obj) && _.keys(obj),
					length = (keys || obj).length;
				for(var index = 0; index < length; index++) {
					var currentKey = keys ? keys[index] : index;
					if(predicate(obj[currentKey], currentKey, obj)) return true;
				}
				return false;
			};

			// Determine if the array or object contains a given item (using `===`).
			// Aliased as `includes` and `include`.
			_.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
				if(!isArrayLike(obj)) obj = _.values(obj);
				if(typeof fromIndex != 'number' || guard) fromIndex = 0;
				return _.indexOf(obj, item, fromIndex) >= 0;
			};

			// Invoke a method (with arguments) on every item in a collection.
			_.invoke = function(obj, method) {
				var args = slice.call(arguments, 2);
				var isFunc = _.isFunction(method);
				return _.map(obj, function(value) {
					var func = isFunc ? method : value[method];
					return func == null ? func : func.apply(value, args);
				});
			};

			// Convenience version of a common use case of `map`: fetching a property.
			_.pluck = function(obj, key) {
				return _.map(obj, _.property(key));
			};

			// Convenience version of a common use case of `filter`: selecting only objects
			// containing specific `key:value` pairs.
			_.where = function(obj, attrs) {
				return _.filter(obj, _.matcher(attrs));
			};

			// Convenience version of a common use case of `find`: getting the first object
			// containing specific `key:value` pairs.
			_.findWhere = function(obj, attrs) {
				return _.find(obj, _.matcher(attrs));
			};

			// Return the maximum element (or element-based computation).
			_.max = function(obj, iteratee, context) {
				var result = -Infinity,
					lastComputed = -Infinity,
					value, computed;
				if(iteratee == null && obj != null) {
					obj = isArrayLike(obj) ? obj : _.values(obj);
					for(var i = 0, length = obj.length; i < length; i++) {
						value = obj[i];
						if(value > result) {
							result = value;
						}
					}
				} else {
					iteratee = cb(iteratee, context);
					_.each(obj, function(value, index, list) {
						computed = iteratee(value, index, list);
						if(computed > lastComputed || computed === -Infinity && result === -Infinity) {
							result = value;
							lastComputed = computed;
						}
					});
				}
				return result;
			};

			// Return the minimum element (or element-based computation).
			_.min = function(obj, iteratee, context) {
				var result = Infinity,
					lastComputed = Infinity,
					value, computed;
				if(iteratee == null && obj != null) {
					obj = isArrayLike(obj) ? obj : _.values(obj);
					for(var i = 0, length = obj.length; i < length; i++) {
						value = obj[i];
						if(value < result) {
							result = value;
						}
					}
				} else {
					iteratee = cb(iteratee, context);
					_.each(obj, function(value, index, list) {
						computed = iteratee(value, index, list);
						if(computed < lastComputed || computed === Infinity && result === Infinity) {
							result = value;
							lastComputed = computed;
						}
					});
				}
				return result;
			};

			// Shuffle a collection, using the modern version of the
			// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
			_.shuffle = function(obj) {
				var set = isArrayLike(obj) ? obj : _.values(obj);
				var length = set.length;
				var shuffled = Array(length);
				for(var index = 0, rand; index < length; index++) {
					rand = _.random(0, index);
					if(rand !== index) shuffled[index] = shuffled[rand];
					shuffled[rand] = set[index];
				}
				return shuffled;
			};

			// Sample **n** random values from a collection.
			// If **n** is not specified, returns a single random element.
			// The internal `guard` argument allows it to work with `map`.
			_.sample = function(obj, n, guard) {
				if(n == null || guard) {
					if(!isArrayLike(obj)) obj = _.values(obj);
					return obj[_.random(obj.length - 1)];
				}
				return _.shuffle(obj).slice(0, Math.max(0, n));
			};

			// Sort the object's values by a criterion produced by an iteratee.
			_.sortBy = function(obj, iteratee, context) {
				iteratee = cb(iteratee, context);
				return _.pluck(_.map(obj, function(value, index, list) {
					return {
						value: value,
						index: index,
						criteria: iteratee(value, index, list)
					};
				}).sort(function(left, right) {
					var a = left.criteria;
					var b = right.criteria;
					if(a !== b) {
						if(a > b || a === void 0) return 1;
						if(a < b || b === void 0) return -1;
					}
					return left.index - right.index;
				}), 'value');
			};

			// An internal function used for aggregate "group by" operations.
			var group = function(behavior) {
				return function(obj, iteratee, context) {
					var result = {};
					iteratee = cb(iteratee, context);
					_.each(obj, function(value, index) {
						var key = iteratee(value, index, obj);
						behavior(result, value, key);
					});
					return result;
				};
			};

			// Groups the object's values by a criterion. Pass either a string attribute
			// to group by, or a function that returns the criterion.
			_.groupBy = group(function(result, value, key) {
				if(_.has(result, key)) result[key].push(value);
				else result[key] = [value];
			});

			// Indexes the object's values by a criterion, similar to `groupBy`, but for
			// when you know that your index values will be unique.
			_.indexBy = group(function(result, value, key) {
				result[key] = value;
			});

			// Counts instances of an object that group by a certain criterion. Pass
			// either a string attribute to count by, or a function that returns the
			// criterion.
			_.countBy = group(function(result, value, key) {
				if(_.has(result, key)) result[key]++;
				else result[key] = 1;
			});

			// Safely create a real, live array from anything iterable.
			_.toArray = function(obj) {
				if(!obj) return [];
				if(_.isArray(obj)) return slice.call(obj);
				if(isArrayLike(obj)) return _.map(obj, _.identity);
				return _.values(obj);
			};

			// Return the number of elements in an object.
			_.size = function(obj) {
				if(obj == null) return 0;
				return isArrayLike(obj) ? obj.length : _.keys(obj).length;
			};

			// Split a collection into two arrays: one whose elements all satisfy the given
			// predicate, and one whose elements all do not satisfy the predicate.
			_.partition = function(obj, predicate, context) {
				predicate = cb(predicate, context);
				var pass = [],
					fail = [];
				_.each(obj, function(value, key, obj) {
					(predicate(value, key, obj) ? pass : fail).push(value);
				});
				return [pass, fail];
			};

			// Array Functions
			// ---------------

			// Get the first element of an array. Passing **n** will return the first N
			// values in the array. Aliased as `head` and `take`. The **guard** check
			// allows it to work with `_.map`.
			_.first = _.head = _.take = function(array, n, guard) {
				if(array == null) return void 0;
				if(n == null || guard) return array[0];
				return _.initial(array, array.length - n);
			};

			// Returns everything but the last entry of the array. Especially useful on
			// the arguments object. Passing **n** will return all the values in
			// the array, excluding the last N.
			_.initial = function(array, n, guard) {
				return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
			};

			// Get the last element of an array. Passing **n** will return the last N
			// values in the array.
			_.last = function(array, n, guard) {
				if(array == null) return void 0;
				if(n == null || guard) return array[array.length - 1];
				return _.rest(array, Math.max(0, array.length - n));
			};

			// Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
			// Especially useful on the arguments object. Passing an **n** will return
			// the rest N values in the array.
			_.rest = _.tail = _.drop = function(array, n, guard) {
				return slice.call(array, n == null || guard ? 1 : n);
			};

			// Trim out all falsy values from an array.
			_.compact = function(array) {
				return _.filter(array, _.identity);
			};

			// Internal implementation of a recursive `flatten` function.
			var flatten = function(input, shallow, strict, startIndex) {
				var output = [],
					idx = 0;
				for(var i = startIndex || 0, length = getLength(input); i < length; i++) {
					var value = input[i];
					if(isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
						//flatten current level of array or arguments object
						if(!shallow) value = flatten(value, shallow, strict);
						var j = 0,
							len = value.length;
						output.length += len;
						while(j < len) {
							output[idx++] = value[j++];
						}
					} else if(!strict) {
						output[idx++] = value;
					}
				}
				return output;
			};

			// Flatten out an array, either recursively (by default), or just one level.
			_.flatten = function(array, shallow) {
				return flatten(array, shallow, false);
			};

			// Return a version of the array that does not contain the specified value(s).
			_.without = function(array) {
				return _.difference(array, slice.call(arguments, 1));
			};

			// Produce a duplicate-free version of the array. If the array has already
			// been sorted, you have the option of using a faster algorithm.
			// Aliased as `unique`.
			_.uniq = _.unique = function(array, isSorted, iteratee, context) {
				if(!_.isBoolean(isSorted)) {
					context = iteratee;
					iteratee = isSorted;
					isSorted = false;
				}
				if(iteratee != null) iteratee = cb(iteratee, context);
				var result = [];
				var seen = [];
				for(var i = 0, length = getLength(array); i < length; i++) {
					var value = array[i],
						computed = iteratee ? iteratee(value, i, array) : value;
					if(isSorted) {
						if(!i || seen !== computed) result.push(value);
						seen = computed;
					} else if(iteratee) {
						if(!_.contains(seen, computed)) {
							seen.push(computed);
							result.push(value);
						}
					} else if(!_.contains(result, value)) {
						result.push(value);
					}
				}
				return result;
			};

			// Produce an array that contains the union: each distinct element from all of
			// the passed-in arrays.
			_.union = function() {
				return _.uniq(flatten(arguments, true, true));
			};

			// Produce an array that contains every item shared between all the
			// passed-in arrays.
			_.intersection = function(array) {
				var result = [];
				var argsLength = arguments.length;
				for(var i = 0, length = getLength(array); i < length; i++) {
					var item = array[i];
					if(_.contains(result, item)) continue;
					for(var j = 1; j < argsLength; j++) {
						if(!_.contains(arguments[j], item)) break;
					}
					if(j === argsLength) result.push(item);
				}
				return result;
			};

			// Take the difference between one array and a number of other arrays.
			// Only the elements present in just the first array will remain.
			_.difference = function(array) {
				var rest = flatten(arguments, true, true, 1);
				return _.filter(array, function(value) {
					return !_.contains(rest, value);
				});
			};

			// Zip together multiple lists into a single array -- elements that share
			// an index go together.
			_.zip = function() {
				return _.unzip(arguments);
			};

			// Complement of _.zip. Unzip accepts an array of arrays and groups
			// each array's elements on shared indices
			_.unzip = function(array) {
				var length = array && _.max(array, getLength).length || 0;
				var result = Array(length);

				for(var index = 0; index < length; index++) {
					result[index] = _.pluck(array, index);
				}
				return result;
			};

			// Converts lists into objects. Pass either a single array of `[key, value]`
			// pairs, or two parallel arrays of the same length -- one of keys, and one of
			// the corresponding values.
			_.object = function(list, values) {
				var result = {};
				for(var i = 0, length = getLength(list); i < length; i++) {
					if(values) {
						result[list[i]] = values[i];
					} else {
						result[list[i][0]] = list[i][1];
					}
				}
				return result;
			};

			// Generator function to create the findIndex and findLastIndex functions
			function createPredicateIndexFinder(dir) {
				return function(array, predicate, context) {
					predicate = cb(predicate, context);
					var length = getLength(array);
					var index = dir > 0 ? 0 : length - 1;
					for(; index >= 0 && index < length; index += dir) {
						if(predicate(array[index], index, array)) return index;
					}
					return -1;
				};
			}

			// Returns the first index on an array-like that passes a predicate test
			_.findIndex = createPredicateIndexFinder(1);
			_.findLastIndex = createPredicateIndexFinder(-1);

			// Use a comparator function to figure out the smallest index at which
			// an object should be inserted so as to maintain order. Uses binary search.
			_.sortedIndex = function(array, obj, iteratee, context) {
				iteratee = cb(iteratee, context, 1);
				var value = iteratee(obj);
				var low = 0,
					high = getLength(array);
				while(low < high) {
					var mid = Math.floor((low + high) / 2);
					if(iteratee(array[mid]) < value) low = mid + 1;
					else high = mid;
				}
				return low;
			};

			// Generator function to create the indexOf and lastIndexOf functions
			function createIndexFinder(dir, predicateFind, sortedIndex) {
				return function(array, item, idx) {
					var i = 0,
						length = getLength(array);
					if(typeof idx == 'number') {
						if(dir > 0) {
							i = idx >= 0 ? idx : Math.max(idx + length, i);
						} else {
							length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
						}
					} else if(sortedIndex && idx && length) {
						idx = sortedIndex(array, item);
						return array[idx] === item ? idx : -1;
					}
					if(item !== item) {
						idx = predicateFind(slice.call(array, i, length), _.isNaN);
						return idx >= 0 ? idx + i : -1;
					}
					for(idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
						if(array[idx] === item) return idx;
					}
					return -1;
				};
			}

			// Return the position of the first occurrence of an item in an array,
			// or -1 if the item is not included in the array.
			// If the array is large and already in sort order, pass `true`
			// for **isSorted** to use binary search.
			_.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
			_.lastIndexOf = createIndexFinder(-1, _.findLastIndex);

			// Generate an integer Array containing an arithmetic progression. A port of
			// the native Python `range()` function. See
			// [the Python documentation](http://docs.python.org/library/functions.html#range).
			_.range = function(start, stop, step) {
				if(stop == null) {
					stop = start || 0;
					start = 0;
				}
				step = step || 1;

				var length = Math.max(Math.ceil((stop - start) / step), 0);
				var range = Array(length);

				for(var idx = 0; idx < length; idx++, start += step) {
					range[idx] = start;
				}

				return range;
			};

			// Function (ahem) Functions
			// ------------------

			// Determines whether to execute a function as a constructor
			// or a normal function with the provided arguments
			var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
				if(!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
				var self = baseCreate(sourceFunc.prototype);
				var result = sourceFunc.apply(self, args);
				if(_.isObject(result)) return result;
				return self;
			};

			// Create a function bound to a given object (assigning `this`, and arguments,
			// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
			// available.
			_.bind = function(func, context) {
				if(nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
				if(!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
				var args = slice.call(arguments, 2);
				var bound = function() {
					return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
				};
				return bound;
			};

			// Partially apply a function by creating a version that has had some of its
			// arguments pre-filled, without changing its dynamic `this` context. _ acts
			// as a placeholder, allowing any combination of arguments to be pre-filled.
			_.partial = function(func) {
				var boundArgs = slice.call(arguments, 1);
				var bound = function() {
					var position = 0,
						length = boundArgs.length;
					var args = Array(length);
					for(var i = 0; i < length; i++) {
						args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i];
					}
					while(position < arguments.length) args.push(arguments[position++]);
					return executeBound(func, bound, this, this, args);
				};
				return bound;
			};

			// Bind a number of an object's methods to that object. Remaining arguments
			// are the method names to be bound. Useful for ensuring that all callbacks
			// defined on an object belong to it.
			_.bindAll = function(obj) {
				var i, length = arguments.length,
					key;
				if(length <= 1) throw new Error('bindAll must be passed function names');
				for(i = 1; i < length; i++) {
					key = arguments[i];
					obj[key] = _.bind(obj[key], obj);
				}
				return obj;
			};

			// Memoize an expensive function by storing its results.
			_.memoize = function(func, hasher) {
				var memoize = function(key) {
					var cache = memoize.cache;
					var address = '' + (hasher ? hasher.apply(this, arguments) : key);
					if(!_.has(cache, address)) cache[address] = func.apply(this, arguments);
					return cache[address];
				};
				memoize.cache = {};
				return memoize;
			};

			// Delays a function for the given number of milliseconds, and then calls
			// it with the arguments supplied.
			_.delay = function(func, wait) {
				var args = slice.call(arguments, 2);
				return setTimeout(function() {
					return func.apply(null, args);
				}, wait);
			};

			// Defers a function, scheduling it to run after the current call stack has
			// cleared.
			_.defer = _.partial(_.delay, _, 1);

			// Returns a function, that, when invoked, will only be triggered at most once
			// during a given window of time. Normally, the throttled function will run
			// as much as it can, without ever going more than once per `wait` duration;
			// but if you'd like to disable the execution on the leading edge, pass
			// `{leading: false}`. To disable execution on the trailing edge, ditto.
			_.throttle = function(func, wait, options) {
				var context, args, result;
				var timeout = null;
				var previous = 0;
				if(!options) options = {};
				var later = function() {
					previous = options.leading === false ? 0 : _.now();
					timeout = null;
					result = func.apply(context, args);
					if(!timeout) context = args = null;
				};
				return function() {
					var now = _.now();
					if(!previous && options.leading === false) previous = now;
					var remaining = wait - (now - previous);
					context = this;
					args = arguments;
					if(remaining <= 0 || remaining > wait) {
						if(timeout) {
							clearTimeout(timeout);
							timeout = null;
						}
						previous = now;
						result = func.apply(context, args);
						if(!timeout) context = args = null;
					} else if(!timeout && options.trailing !== false) {
						timeout = setTimeout(later, remaining);
					}
					return result;
				};
			};

			// Returns a function, that, as long as it continues to be invoked, will not
			// be triggered. The function will be called after it stops being called for
			// N milliseconds. If `immediate` is passed, trigger the function on the
			// leading edge, instead of the trailing.
			_.debounce = function(func, wait, immediate) {
				var timeout, args, context, timestamp, result;

				var later = function() {
					var last = _.now() - timestamp;

					if(last < wait && last >= 0) {
						timeout = setTimeout(later, wait - last);
					} else {
						timeout = null;
						if(!immediate) {
							result = func.apply(context, args);
							if(!timeout) context = args = null;
						}
					}
				};

				return function() {
					context = this;
					args = arguments;
					timestamp = _.now();
					var callNow = immediate && !timeout;
					if(!timeout) timeout = setTimeout(later, wait);
					if(callNow) {
						result = func.apply(context, args);
						context = args = null;
					}

					return result;
				};
			};

			// Returns the first function passed as an argument to the second,
			// allowing you to adjust arguments, run code before and after, and
			// conditionally execute the original function.
			_.wrap = function(func, wrapper) {
				return _.partial(wrapper, func);
			};

			// Returns a negated version of the passed-in predicate.
			_.negate = function(predicate) {
				return function() {
					return !predicate.apply(this, arguments);
				};
			};

			// Returns a function that is the composition of a list of functions, each
			// consuming the return value of the function that follows.
			_.compose = function() {
				var args = arguments;
				var start = args.length - 1;
				return function() {
					var i = start;
					var result = args[start].apply(this, arguments);
					while(i--) result = args[i].call(this, result);
					return result;
				};
			};

			// Returns a function that will only be executed on and after the Nth call.
			_.after = function(times, func) {
				return function() {
					if(--times < 1) {
						return func.apply(this, arguments);
					}
				};
			};

			// Returns a function that will only be executed up to (but not including) the Nth call.
			_.before = function(times, func) {
				var memo;
				return function() {
					if(--times > 0) {
						memo = func.apply(this, arguments);
					}
					if(times <= 1) func = null;
					return memo;
				};
			};

			// Returns a function that will be executed at most one time, no matter how
			// often you call it. Useful for lazy initialization.
			_.once = _.partial(_.before, 2);

			// Object Functions
			// ----------------

			// Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
			var hasEnumBug = !{
				toString: null
			}.propertyIsEnumerable('toString');
			var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
				'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'
			];

			function collectNonEnumProps(obj, keys) {
				var nonEnumIdx = nonEnumerableProps.length;
				var constructor = obj.constructor;
				var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;

				// Constructor is a special case.
				var prop = 'constructor';
				if(_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

				while(nonEnumIdx--) {
					prop = nonEnumerableProps[nonEnumIdx];
					if(prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
						keys.push(prop);
					}
				}
			}

			// Retrieve the names of an object's own properties.
			// Delegates to **ECMAScript 5**'s native `Object.keys`
			_.keys = function(obj) {
				if(!_.isObject(obj)) return [];
				if(nativeKeys) return nativeKeys(obj);
				var keys = [];
				for(var key in obj)
					if(_.has(obj, key)) keys.push(key);
					// Ahem, IE < 9.
				if(hasEnumBug) collectNonEnumProps(obj, keys);
				return keys;
			};

			// Retrieve all the property names of an object.
			_.allKeys = function(obj) {
				if(!_.isObject(obj)) return [];
				var keys = [];
				for(var key in obj) keys.push(key);
				// Ahem, IE < 9.
				if(hasEnumBug) collectNonEnumProps(obj, keys);
				return keys;
			};

			// Retrieve the values of an object's properties.
			_.values = function(obj) {
				var keys = _.keys(obj);
				var length = keys.length;
				var values = Array(length);
				for(var i = 0; i < length; i++) {
					values[i] = obj[keys[i]];
				}
				return values;
			};

			// Returns the results of applying the iteratee to each element of the object
			// In contrast to _.map it returns an object
			_.mapObject = function(obj, iteratee, context) {
				iteratee = cb(iteratee, context);
				var keys = _.keys(obj),
					length = keys.length,
					results = {},
					currentKey;
				for(var index = 0; index < length; index++) {
					currentKey = keys[index];
					results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
				}
				return results;
			};

			// Convert an object into a list of `[key, value]` pairs.
			_.pairs = function(obj) {
				var keys = _.keys(obj);
				var length = keys.length;
				var pairs = Array(length);
				for(var i = 0; i < length; i++) {
					pairs[i] = [keys[i], obj[keys[i]]];
				}
				return pairs;
			};

			// Invert the keys and values of an object. The values must be serializable.
			_.invert = function(obj) {
				var result = {};
				var keys = _.keys(obj);
				for(var i = 0, length = keys.length; i < length; i++) {
					result[obj[keys[i]]] = keys[i];
				}
				return result;
			};

			// Return a sorted list of the function names available on the object.
			// Aliased as `methods`
			_.functions = _.methods = function(obj) {
				var names = [];
				for(var key in obj) {
					if(_.isFunction(obj[key])) names.push(key);
				}
				return names.sort();
			};

			// Extend a given object with all the properties in passed-in object(s).
			_.extend = createAssigner(_.allKeys);

			// Assigns a given object with all the own properties in the passed-in object(s)
			// (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
			_.extendOwn = _.assign = createAssigner(_.keys);

			// Returns the first key on an object that passes a predicate test
			_.findKey = function(obj, predicate, context) {
				predicate = cb(predicate, context);
				var keys = _.keys(obj),
					key;
				for(var i = 0, length = keys.length; i < length; i++) {
					key = keys[i];
					if(predicate(obj[key], key, obj)) return key;
				}
			};

			// Return a copy of the object only containing the whitelisted properties.
			_.pick = function(object, oiteratee, context) {
				var result = {},
					obj = object,
					iteratee, keys;
				if(obj == null) return result;
				if(_.isFunction(oiteratee)) {
					keys = _.allKeys(obj);
					iteratee = optimizeCb(oiteratee, context);
				} else {
					keys = flatten(arguments, false, false, 1);
					iteratee = function(value, key, obj) {
						return key in obj;
					};
					obj = Object(obj);
				}
				for(var i = 0, length = keys.length; i < length; i++) {
					var key = keys[i];
					var value = obj[key];
					if(iteratee(value, key, obj)) result[key] = value;
				}
				return result;
			};

			// Return a copy of the object without the blacklisted properties.
			_.omit = function(obj, iteratee, context) {
				if(_.isFunction(iteratee)) {
					iteratee = _.negate(iteratee);
				} else {
					var keys = _.map(flatten(arguments, false, false, 1), String);
					iteratee = function(value, key) {
						return !_.contains(keys, key);
					};
				}
				return _.pick(obj, iteratee, context);
			};

			// Fill in a given object with default properties.
			_.defaults = createAssigner(_.allKeys, true);

			// Creates an object that inherits from the given prototype object.
			// If additional properties are provided then they will be added to the
			// created object.
			_.create = function(prototype, props) {
				var result = baseCreate(prototype);
				if(props) _.extendOwn(result, props);
				return result;
			};

			// Create a (shallow-cloned) duplicate of an object.
			_.clone = function(obj) {
				if(!_.isObject(obj)) return obj;
				return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
			};

			// Invokes interceptor with the obj, and then returns obj.
			// The primary purpose of this method is to "tap into" a method chain, in
			// order to perform operations on intermediate results within the chain.
			_.tap = function(obj, interceptor) {
				interceptor(obj);
				return obj;
			};

			// Returns whether an object has a given set of `key:value` pairs.
			_.isMatch = function(object, attrs) {
				var keys = _.keys(attrs),
					length = keys.length;
				if(object == null) return !length;
				var obj = Object(object);
				for(var i = 0; i < length; i++) {
					var key = keys[i];
					if(attrs[key] !== obj[key] || !(key in obj)) return false;
				}
				return true;
			};

			// Internal recursive comparison function for `isEqual`.
			var eq = function(a, b, aStack, bStack) {
				// Identical objects are equal. `0 === -0`, but they aren't identical.
				// See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
				if(a === b) return a !== 0 || 1 / a === 1 / b;
				// A strict comparison is necessary because `null == undefined`.
				if(a == null || b == null) return a === b;
				// Unwrap any wrapped objects.
				if(a instanceof _) a = a._wrapped;
				if(b instanceof _) b = b._wrapped;
				// Compare `[[Class]]` names.
				var className = toString.call(a);
				if(className !== toString.call(b)) return false;
				switch(className) {
					// Strings, numbers, regular expressions, dates, and booleans are compared by value.
					case '[object RegExp]':
						// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
					case '[object String]':
						// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
						// equivalent to `new String("5")`.
						return '' + a === '' + b;
					case '[object Number]':
						// `NaN`s are equivalent, but non-reflexive.
						// Object(NaN) is equivalent to NaN
						if(+a !== +a) return +b !== +b;
						// An `egal` comparison is performed for other numeric values.
						return +a === 0 ? 1 / +a === 1 / b : +a === +b;
					case '[object Date]':
					case '[object Boolean]':
						// Coerce dates and booleans to numeric primitive values. Dates are compared by their
						// millisecond representations. Note that invalid dates with millisecond representations
						// of `NaN` are not equivalent.
						return +a === +b;
				}

				var areArrays = className === '[object Array]';
				if(!areArrays) {
					if(typeof a != 'object' || typeof b != 'object') return false;

					// Objects with different constructors are not equivalent, but `Object`s or `Array`s
					// from different frames are.
					var aCtor = a.constructor,
						bCtor = b.constructor;
					if(aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
							_.isFunction(bCtor) && bCtor instanceof bCtor) &&
						('constructor' in a && 'constructor' in b)) {
						return false;
					}
				}
				// Assume equality for cyclic structures. The algorithm for detecting cyclic
				// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.

				// Initializing stack of traversed objects.
				// It's done here since we only need them for objects and arrays comparison.
				aStack = aStack || [];
				bStack = bStack || [];
				var length = aStack.length;
				while(length--) {
					// Linear search. Performance is inversely proportional to the number of
					// unique nested structures.
					if(aStack[length] === a) return bStack[length] === b;
				}

				// Add the first object to the stack of traversed objects.
				aStack.push(a);
				bStack.push(b);

				// Recursively compare objects and arrays.
				if(areArrays) {
					// Compare array lengths to determine if a deep comparison is necessary.
					length = a.length;
					if(length !== b.length) return false;
					// Deep compare the contents, ignoring non-numeric properties.
					while(length--) {
						if(!eq(a[length], b[length], aStack, bStack)) return false;
					}
				} else {
					// Deep compare objects.
					var keys = _.keys(a),
						key;
					length = keys.length;
					// Ensure that both objects contain the same number of properties before comparing deep equality.
					if(_.keys(b).length !== length) return false;
					while(length--) {
						// Deep compare each member
						key = keys[length];
						if(!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
					}
				}
				// Remove the first object from the stack of traversed objects.
				aStack.pop();
				bStack.pop();
				return true;
			};

			// Perform a deep comparison to check if two objects are equal.
			_.isEqual = function(a, b) {
				return eq(a, b);
			};

			// Is a given array, string, or object empty?
			// An "empty" object has no enumerable own-properties.
			_.isEmpty = function(obj) {
				if(obj == null) return true;
				if(isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
				return _.keys(obj).length === 0;
			};

			// Is a given value a DOM element?
			_.isElement = function(obj) {
				return !!(obj && obj.nodeType === 1);
			};

			// Is a given value an array?
			// Delegates to ECMA5's native Array.isArray
			_.isArray = nativeIsArray || function(obj) {
				return toString.call(obj) === '[object Array]';
			};

			// Is a given variable an object?
			_.isObject = function(obj) {
				var type = typeof obj;
				return type === 'function' || type === 'object' && !!obj;
			};

			// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError.
			_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
				_['is' + name] = function(obj) {
					return toString.call(obj) === '[object ' + name + ']';
				};
			});

			// Define a fallback version of the method in browsers (ahem, IE < 9), where
			// there isn't any inspectable "Arguments" type.
			if(!_.isArguments(arguments)) {
				_.isArguments = function(obj) {
					return _.has(obj, 'callee');
				};
			}

			// Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
			// IE 11 (#1621), and in Safari 8 (#1929).
			if(typeof /./ != 'function' && typeof Int8Array != 'object') {
				_.isFunction = function(obj) {
					return typeof obj == 'function' || false;
				};
			}

			// Is a given object a finite number?
			_.isFinite = function(obj) {
				return isFinite(obj) && !isNaN(parseFloat(obj));
			};

			// Is the given value `NaN`? (NaN is the only number which does not equal itself).
			_.isNaN = function(obj) {
				return _.isNumber(obj) && obj !== +obj;
			};

			// Is a given value a boolean?
			_.isBoolean = function(obj) {
				return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
			};

			// Is a given value equal to null?
			_.isNull = function(obj) {
				return obj === null;
			};

			// Is a given variable undefined?
			_.isUndefined = function(obj) {
				return obj === void 0;
			};

			// Shortcut function for checking if an object has a given property directly
			// on itself (in other words, not on a prototype).
			_.has = function(obj, key) {
				return obj != null && hasOwnProperty.call(obj, key);
			};

			// Utility Functions
			// -----------------

			// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
			// previous owner. Returns a reference to the Underscore object.
			_.noConflict = function() {
				root._ = previousUnderscore;
				return this;
			};

			// Keep the identity function around for default iteratees.
			_.identity = function(value) {
				return value;
			};

			// Predicate-generating functions. Often useful outside of Underscore.
			_.constant = function(value) {
				return function() {
					return value;
				};
			};

			_.noop = function() {};

			_.property = property;

			// Generates a function for a given object that returns a given property.
			_.propertyOf = function(obj) {
				return obj == null ? function() {} : function(key) {
					return obj[key];
				};
			};

			// Returns a predicate for checking whether an object has a given set of
			// `key:value` pairs.
			_.matcher = _.matches = function(attrs) {
				attrs = _.extendOwn({}, attrs);
				return function(obj) {
					return _.isMatch(obj, attrs);
				};
			};

			// Run a function **n** times.
			_.times = function(n, iteratee, context) {
				var accum = Array(Math.max(0, n));
				iteratee = optimizeCb(iteratee, context, 1);
				for(var i = 0; i < n; i++) accum[i] = iteratee(i);
				return accum;
			};

			// Return a random integer between min and max (inclusive).
			_.random = function(min, max) {
				if(max == null) {
					max = min;
					min = 0;
				}
				return min + Math.floor(Math.random() * (max - min + 1));
			};

			// A (possibly faster) way to get the current timestamp as an integer.
			_.now = Date.now || function() {
				return new Date().getTime();
			};

			// List of HTML entities for escaping.
			var escapeMap = {
				'&': '&amp;',
				'<': '&lt;',
				'>': '&gt;',
				'"': '&quot;',
				"'": '&#x27;',
				'`': '&#x60;'
			};
			var unescapeMap = _.invert(escapeMap);

			// Functions for escaping and unescaping strings to/from HTML interpolation.
			var createEscaper = function(map) {
				var escaper = function(match) {
					return map[match];
				};
				// Regexes for identifying a key that needs to be escaped
				var source = '(?:' + _.keys(map).join('|') + ')';
				var testRegexp = RegExp(source);
				var replaceRegexp = RegExp(source, 'g');
				return function(string) {
					string = string == null ? '' : '' + string;
					return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
				};
			};
			_.escape = createEscaper(escapeMap);
			_.unescape = createEscaper(unescapeMap);

			// If the value of the named `property` is a function then invoke it with the
			// `object` as context; otherwise, return it.
			_.result = function(object, property, fallback) {
				var value = object == null ? void 0 : object[property];
				if(value === void 0) {
					value = fallback;
				}
				return _.isFunction(value) ? value.call(object) : value;
			};

			// Generate a unique integer id (unique within the entire client session).
			// Useful for temporary DOM ids.
			var idCounter = 0;
			_.uniqueId = function(prefix) {
				var id = ++idCounter + '';
				return prefix ? prefix + id : id;
			};

			// By default, Underscore uses ERB-style template delimiters, change the
			// following template settings to use alternative delimiters.
			_.templateSettings = {
				evaluate: /<%([\s\S]+?)%>/g,
				interpolate: /<%=([\s\S]+?)%>/g,
				escape: /<%-([\s\S]+?)%>/g
			};

			// When customizing `templateSettings`, if you don't want to define an
			// interpolation, evaluation or escaping regex, we need one that is
			// guaranteed not to match.
			var noMatch = /(.)^/;

			// Certain characters need to be escaped so that they can be put into a
			// string literal.
			var escapes = {
				"'": "'",
				'\\': '\\',
				'\r': 'r',
				'\n': 'n',
				'\u2028': 'u2028',
				'\u2029': 'u2029'
			};

			var escaper = /\\|'|\r|\n|\u2028|\u2029/g;

			var escapeChar = function(match) {
				return '\\' + escapes[match];
			};

			// JavaScript micro-templating, similar to John Resig's implementation.
			// Underscore templating handles arbitrary delimiters, preserves whitespace,
			// and correctly escapes quotes within interpolated code.
			// NB: `oldSettings` only exists for backwards compatibility.
			_.template = function(text, settings, oldSettings) {
				if(!settings && oldSettings) settings = oldSettings;
				settings = _.defaults({}, settings, _.templateSettings);

				// Combine delimiters into one regular expression via alternation.
				var matcher = RegExp([
					(settings.escape || noMatch).source,
					(settings.interpolate || noMatch).source,
					(settings.evaluate || noMatch).source
				].join('|') + '|$', 'g');

				// Compile the template source, escaping string literals appropriately.
				var index = 0;
				var source = "__p+='";
				text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
					source += text.slice(index, offset).replace(escaper, escapeChar);
					index = offset + match.length;

					if(escape) {
						source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
					} else if(interpolate) {
						source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
					} else if(evaluate) {
						source += "';\n" + evaluate + "\n__p+='";
					}

					// Adobe VMs need the match returned to produce the correct offest.
					return match;
				});
				source += "';\n";

				// If a variable is not specified, place data values in local scope.
				if(!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';

				source = "var __t,__p='',__j=Array.prototype.join," +
					"print=function(){__p+=__j.call(arguments,'');};\n" +
					source + 'return __p;\n';

				try {
					var render = new Function(settings.variable || 'obj', '_', source);
				} catch(e) {
					e.source = source;
					throw e;
				}

				var template = function(data) {
					return render.call(this, data, _);
				};

				// Provide the compiled source as a convenience for precompilation.
				var argument = settings.variable || 'obj';
				template.source = 'function(' + argument + '){\n' + source + '}';

				return template;
			};

			// Add a "chain" function. Start chaining a wrapped Underscore object.
			_.chain = function(obj) {
				var instance = _(obj);
				instance._chain = true;
				return instance;
			};

			// OOP
			// ---------------
			// If Underscore is called as a function, it returns a wrapped object that
			// can be used OO-style. This wrapper holds altered versions of all the
			// underscore functions. Wrapped objects may be chained.

			// Helper function to continue chaining intermediate results.
			var result = function(instance, obj) {
				return instance._chain ? _(obj).chain() : obj;
			};

			// Add your own custom functions to the Underscore object.
			_.mixin = function(obj) {
				_.each(_.functions(obj), function(name) {
					var func = _[name] = obj[name];
					_.prototype[name] = function() {
						var args = [this._wrapped];
						push.apply(args, arguments);
						return result(this, func.apply(_, args));
					};
				});
			};

			// Add all of the Underscore functions to the wrapper object.
			_.mixin(_);

			// Add all mutator Array functions to the wrapper.
			_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
				var method = ArrayProto[name];
				_.prototype[name] = function() {
					var obj = this._wrapped;
					method.apply(obj, arguments);
					if((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
					return result(this, obj);
				};
			});

			// Add all accessor Array functions to the wrapper.
			_.each(['concat', 'join', 'slice'], function(name) {
				var method = ArrayProto[name];
				_.prototype[name] = function() {
					return result(this, method.apply(this._wrapped, arguments));
				};
			});

			// Extracts the result from a wrapped and chained object.
			_.prototype.value = function() {
				return this._wrapped;
			};

			// Provide unwrapping proxy for some methods used in engine operations
			// such as arithmetic and JSON stringification.
			_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;

			_.prototype.toString = function() {
				return '' + this._wrapped;
			};

			// AMD registration happens at the end for compatibility with AMD loaders
			// that may not enforce next-turn semantics on modules. Even though general
			// practice for AMD registration is to be anonymous, underscore registers
			// as a named module because, like jQuery, it is a base library that is
			// popular enough to be bundled in a third party lib, but not be part of
			// an AMD load request. Those cases could generate an error when an
			// anonymous define() is called outside of a loader request.
			if(typeof define === 'function' && define.amd) {
				define('underscore', [], function() {
					return _;
				});
			}
		}.call(this));

	}, {}],
	6: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function(require) {

				var makePromise = require('./makePromise');
				var Scheduler = require('./Scheduler');
				var async = require('./env').asap;

				return makePromise({
					scheduler: new Scheduler(async)
				});

			});
		})(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory(require);
		});

	}, {
		"./Scheduler": 7,
		"./env": 19,
		"./makePromise": 21
	}],
	7: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function() {

				// Credit to Twisol (https://github.com/Twisol) for suggesting
				// this type of extensible queue + trampoline approach for next-tick conflation.

				/**
				 * Async task scheduler
				 * @param {function} async function to schedule a single async function
				 * @constructor
				 */
				function Scheduler(async) {
					this._async = async;
					this._running = false;

					this._queue = this;
					this._queueLen = 0;
					this._afterQueue = {};
					this._afterQueueLen = 0;

					var self = this;
					this.drain = function() {
						self._drain();
					};
				}

				/**
				 * Enqueue a task
				 * @param {{ run:function }} task
				 */
				Scheduler.prototype.enqueue = function(task) {
					this._queue[this._queueLen++] = task;
					this.run();
				};

				/**
				 * Enqueue a task to run after the main task queue
				 * @param {{ run:function }} task
				 */
				Scheduler.prototype.afterQueue = function(task) {
					this._afterQueue[this._afterQueueLen++] = task;
					this.run();
				};

				Scheduler.prototype.run = function() {
					if(!this._running) {
						this._running = true;
						this._async(this.drain);
					}
				};

				/**
				 * Drain the handler queue entirely, and then the after queue
				 */
				Scheduler.prototype._drain = function() {
					var i = 0;
					for(; i < this._queueLen; ++i) {
						this._queue[i].run();
						this._queue[i] = void 0;
					}

					this._queueLen = 0;
					this._running = false;

					for(i = 0; i < this._afterQueueLen; ++i) {
						this._afterQueue[i].run();
						this._afterQueue[i] = void 0;
					}

					this._afterQueueLen = 0;
				};

				return Scheduler;

			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory();
		}));

	}, {}],
	8: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function() {

				/**
				 * Custom error type for promises rejected by promise.timeout
				 * @param {string} message
				 * @constructor
				 */
				function TimeoutError(message) {
					Error.call(this);
					this.message = message;
					this.name = TimeoutError.name;
					if(typeof Error.captureStackTrace === 'function') {
						Error.captureStackTrace(this, TimeoutError);
					}
				}

				TimeoutError.prototype = Object.create(Error.prototype);
				TimeoutError.prototype.constructor = TimeoutError;

				return TimeoutError;
			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory();
		}));
	}, {}],
	9: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function() {

				makeApply.tryCatchResolve = tryCatchResolve;

				return makeApply;

				function makeApply(Promise, call) {
					if(arguments.length < 2) {
						call = tryCatchResolve;
					}

					return apply;

					function apply(f, thisArg, args) {
						var p = Promise._defer();
						var l = args.length;
						var params = new Array(l);
						callAndResolve({
							f: f,
							thisArg: thisArg,
							args: args,
							params: params,
							i: l - 1,
							call: call
						}, p._handler);

						return p;
					}

					function callAndResolve(c, h) {
						if(c.i < 0) {
							return call(c.f, c.thisArg, c.params, h);
						}

						var handler = Promise._handler(c.args[c.i]);
						handler.fold(callAndResolveNext, c, void 0, h);
					}

					function callAndResolveNext(c, x, h) {
						c.params[c.i] = x;
						c.i -= 1;
						callAndResolve(c, h);
					}
				}

				function tryCatchResolve(f, thisArg, args, resolver) {
					try {
						resolver.resolve(f.apply(thisArg, args));
					} catch(e) {
						resolver.reject(e);
					}
				}

			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory();
		}));

	}, {}],
	10: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function(require) {

				var state = require('../state');
				var applier = require('../apply');

				return function array(Promise) {

					var applyFold = applier(Promise);
					var toPromise = Promise.resolve;
					var all = Promise.all;

					var ar = Array.prototype.reduce;
					var arr = Array.prototype.reduceRight;
					var slice = Array.prototype.slice;

					// Additional array combinators

					Promise.any = any;
					Promise.some = some;
					Promise.settle = settle;

					Promise.map = map;
					Promise.filter = filter;
					Promise.reduce = reduce;
					Promise.reduceRight = reduceRight;

					/**
					 * When this promise fulfills with an array, do
					 * onFulfilled.apply(void 0, array)
					 * @param {function} onFulfilled function to apply
					 * @returns {Promise} promise for the result of applying onFulfilled
					 */
					Promise.prototype.spread = function(onFulfilled) {
						return this.then(all).then(function(array) {
							return onFulfilled.apply(this, array);
						});
					};

					return Promise;

					/**
					 * One-winner competitive race.
					 * Return a promise that will fulfill when one of the promises
					 * in the input array fulfills, or will reject when all promises
					 * have rejected.
					 * @param {array} promises
					 * @returns {Promise} promise for the first fulfilled value
					 */
					function any(promises) {
						var p = Promise._defer();
						var resolver = p._handler;
						var l = promises.length >>> 0;

						var pending = l;
						var errors = [];

						for(var h, x, i = 0; i < l; ++i) {
							x = promises[i];
							if(x === void 0 && !(i in promises)) {
								--pending;
								continue;
							}

							h = Promise._handler(x);
							if(h.state() > 0) {
								resolver.become(h);
								Promise._visitRemaining(promises, i, h);
								break;
							} else {
								h.visit(resolver, handleFulfill, handleReject);
							}
						}

						if(pending === 0) {
							resolver.reject(new RangeError('any(): array must not be empty'));
						}

						return p;

						function handleFulfill(x) {
							/*jshint validthis:true*/
							errors = null;
							this.resolve(x); // this === resolver
						}

						function handleReject(e) {
							/*jshint validthis:true*/
							if(this.resolved) { // this === resolver
								return;
							}

							errors.push(e);
							if(--pending === 0) {
								this.reject(errors);
							}
						}
					}

					/**
					 * N-winner competitive race
					 * Return a promise that will fulfill when n input promises have
					 * fulfilled, or will reject when it becomes impossible for n
					 * input promises to fulfill (ie when promises.length - n + 1
					 * have rejected)
					 * @param {array} promises
					 * @param {number} n
					 * @returns {Promise} promise for the earliest n fulfillment values
					 *
					 * @deprecated
					 */
					function some(promises, n) {
						/*jshint maxcomplexity:7*/
						var p = Promise._defer();
						var resolver = p._handler;

						var results = [];
						var errors = [];

						var l = promises.length >>> 0;
						var nFulfill = 0;
						var nReject;
						var x, i; // reused in both for() loops

						// First pass: count actual array items
						for(i = 0; i < l; ++i) {
							x = promises[i];
							if(x === void 0 && !(i in promises)) {
								continue;
							}
							++nFulfill;
						}

						// Compute actual goals
						n = Math.max(n, 0);
						nReject = (nFulfill - n + 1);
						nFulfill = Math.min(n, nFulfill);

						if(n > nFulfill) {
							resolver.reject(new RangeError('some(): array must contain at least ' +
								n + ' item(s), but had ' + nFulfill));
						} else if(nFulfill === 0) {
							resolver.resolve(results);
						}

						// Second pass: observe each array item, make progress toward goals
						for(i = 0; i < l; ++i) {
							x = promises[i];
							if(x === void 0 && !(i in promises)) {
								continue;
							}

							Promise._handler(x).visit(resolver, fulfill, reject, resolver.notify);
						}

						return p;

						function fulfill(x) {
							/*jshint validthis:true*/
							if(this.resolved) { // this === resolver
								return;
							}

							results.push(x);
							if(--nFulfill === 0) {
								errors = null;
								this.resolve(results);
							}
						}

						function reject(e) {
							/*jshint validthis:true*/
							if(this.resolved) { // this === resolver
								return;
							}

							errors.push(e);
							if(--nReject === 0) {
								results = null;
								this.reject(errors);
							}
						}
					}

					/**
					 * Apply f to the value of each promise in a list of promises
					 * and return a new list containing the results.
					 * @param {array} promises
					 * @param {function(x:*, index:Number):*} f mapping function
					 * @returns {Promise}
					 */
					function map(promises, f) {
						return Promise._traverse(f, promises);
					}

					/**
					 * Filter the provided array of promises using the provided predicate.  Input may
					 * contain promises and values
					 * @param {Array} promises array of promises and values
					 * @param {function(x:*, index:Number):boolean} predicate filtering predicate.
					 *  Must return truthy (or promise for truthy) for items to retain.
					 * @returns {Promise} promise that will fulfill with an array containing all items
					 *  for which predicate returned truthy.
					 */
					function filter(promises, predicate) {
						var a = slice.call(promises);
						return Promise._traverse(predicate, a).then(function(keep) {
							return filterSync(a, keep);
						});
					}

					function filterSync(promises, keep) {
						// Safe because we know all promises have fulfilled if we've made it this far
						var l = keep.length;
						var filtered = new Array(l);
						for(var i = 0, j = 0; i < l; ++i) {
							if(keep[i]) {
								filtered[j++] = Promise._handler(promises[i]).value;
							}
						}
						filtered.length = j;
						return filtered;

					}

					/**
					 * Return a promise that will always fulfill with an array containing
					 * the outcome states of all input promises.  The returned promise
					 * will never reject.
					 * @param {Array} promises
					 * @returns {Promise} promise for array of settled state descriptors
					 */
					function settle(promises) {
						return all(promises.map(settleOne));
					}

					function settleOne(p) {
						var h = Promise._handler(p);
						if(h.state() === 0) {
							return toPromise(p).then(state.fulfilled, state.rejected);
						}

						h._unreport();
						return state.inspect(h);
					}

					/**
					 * Traditional reduce function, similar to `Array.prototype.reduce()`, but
					 * input may contain promises and/or values, and reduceFunc
					 * may return either a value or a promise, *and* initialValue may
					 * be a promise for the starting value.
					 * @param {Array|Promise} promises array or promise for an array of anything,
					 *      may contain a mix of promises and values.
					 * @param {function(accumulated:*, x:*, index:Number):*} f reduce function
					 * @returns {Promise} that will resolve to the final reduced value
					 */
					function reduce(promises, f /*, initialValue */ ) {
						return arguments.length > 2 ? ar.call(promises, liftCombine(f), arguments[2]) :
							ar.call(promises, liftCombine(f));
					}

					/**
					 * Traditional reduce function, similar to `Array.prototype.reduceRight()`, but
					 * input may contain promises and/or values, and reduceFunc
					 * may return either a value or a promise, *and* initialValue may
					 * be a promise for the starting value.
					 * @param {Array|Promise} promises array or promise for an array of anything,
					 *      may contain a mix of promises and values.
					 * @param {function(accumulated:*, x:*, index:Number):*} f reduce function
					 * @returns {Promise} that will resolve to the final reduced value
					 */
					function reduceRight(promises, f /*, initialValue */ ) {
						return arguments.length > 2 ? arr.call(promises, liftCombine(f), arguments[2]) :
							arr.call(promises, liftCombine(f));
					}

					function liftCombine(f) {
						return function(z, x, i) {
							return applyFold(f, void 0, [z, x, i]);
						};
					}
				};

			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory(require);
		}));

	}, {
		"../apply": 9,
		"../state": 22
	}],
	11: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function() {

				return function flow(Promise) {

					var resolve = Promise.resolve;
					var reject = Promise.reject;
					var origCatch = Promise.prototype['catch'];

					/**
					 * Handle the ultimate fulfillment value or rejection reason, and assume
					 * responsibility for all errors.  If an error propagates out of result
					 * or handleFatalError, it will be rethrown to the host, resulting in a
					 * loud stack track on most platforms and a crash on some.
					 * @param {function?} onResult
					 * @param {function?} onError
					 * @returns {undefined}
					 */
					Promise.prototype.done = function(onResult, onError) {
						this._handler.visit(this._handler.receiver, onResult, onError);
					};

					/**
					 * Add Error-type and predicate matching to catch.  Examples:
					 * promise.catch(TypeError, handleTypeError)
					 *   .catch(predicate, handleMatchedErrors)
					 *   .catch(handleRemainingErrors)
					 * @param onRejected
					 * @returns {*}
					 */
					Promise.prototype['catch'] = Promise.prototype.otherwise = function(onRejected) {
						if(arguments.length < 2) {
							return origCatch.call(this, onRejected);
						}

						if(typeof onRejected !== 'function') {
							return this.ensure(rejectInvalidPredicate);
						}

						return origCatch.call(this, createCatchFilter(arguments[1], onRejected));
					};

					/**
					 * Wraps the provided catch handler, so that it will only be called
					 * if the predicate evaluates truthy
					 * @param {?function} handler
					 * @param {function} predicate
					 * @returns {function} conditional catch handler
					 */
					function createCatchFilter(handler, predicate) {
						return function(e) {
							return evaluatePredicate(e, predicate) ?
								handler.call(this, e) :
								reject(e);
						};
					}

					/**
					 * Ensures that onFulfilledOrRejected will be called regardless of whether
					 * this promise is fulfilled or rejected.  onFulfilledOrRejected WILL NOT
					 * receive the promises' value or reason.  Any returned value will be disregarded.
					 * onFulfilledOrRejected may throw or return a rejected promise to signal
					 * an additional error.
					 * @param {function} handler handler to be called regardless of
					 *  fulfillment or rejection
					 * @returns {Promise}
					 */
					Promise.prototype['finally'] = Promise.prototype.ensure = function(handler) {
						if(typeof handler !== 'function') {
							return this;
						}

						return this.then(function(x) {
							return runSideEffect(handler, this, identity, x);
						}, function(e) {
							return runSideEffect(handler, this, reject, e);
						});
					};

					function runSideEffect(handler, thisArg, propagate, value) {
						var result = handler.call(thisArg);
						return maybeThenable(result) ?
							propagateValue(result, propagate, value) :
							propagate(value);
					}

					function propagateValue(result, propagate, x) {
						return resolve(result).then(function() {
							return propagate(x);
						});
					}

					/**
					 * Recover from a failure by returning a defaultValue.  If defaultValue
					 * is a promise, it's fulfillment value will be used.  If defaultValue is
					 * a promise that rejects, the returned promise will reject with the
					 * same reason.
					 * @param {*} defaultValue
					 * @returns {Promise} new promise
					 */
					Promise.prototype['else'] = Promise.prototype.orElse = function(defaultValue) {
						return this.then(void 0, function() {
							return defaultValue;
						});
					};

					/**
					 * Shortcut for .then(function() { return value; })
					 * @param  {*} value
					 * @return {Promise} a promise that:
					 *  - is fulfilled if value is not a promise, or
					 *  - if value is a promise, will fulfill with its value, or reject
					 *    with its reason.
					 */
					Promise.prototype['yield'] = function(value) {
						return this.then(function() {
							return value;
						});
					};

					/**
					 * Runs a side effect when this promise fulfills, without changing the
					 * fulfillment value.
					 * @param {function} onFulfilledSideEffect
					 * @returns {Promise}
					 */
					Promise.prototype.tap = function(onFulfilledSideEffect) {
						return this.then(onFulfilledSideEffect)['yield'](this);
					};

					return Promise;
				};

				function rejectInvalidPredicate() {
					throw new TypeError('catch predicate must be a function');
				}

				function evaluatePredicate(e, predicate) {
					return isError(predicate) ? e instanceof predicate : predicate(e);
				}

				function isError(predicate) {
					return predicate === Error ||
						(predicate != null && predicate.prototype instanceof Error);
				}

				function maybeThenable(x) {
					return(typeof x === 'object' || typeof x === 'function') && x !== null;
				}

				function identity(x) {
					return x;
				}

			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory();
		}));

	}, {}],
	12: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */
		/** @author Jeff Escalante */

		(function(define) {
			'use strict';
			define(function() {

				return function fold(Promise) {

					Promise.prototype.fold = function(f, z) {
						var promise = this._beget();

						this._handler.fold(function(z, x, to) {
							Promise._handler(z).fold(function(x, z, to) {
								to.resolve(f.call(this, z, x));
							}, x, this, to);
						}, z, promise._handler.receiver, promise._handler);

						return promise;
					};

					return Promise;
				};

			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory();
		}));

	}, {}],
	13: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function(require) {

				var inspect = require('../state').inspect;

				return function inspection(Promise) {

					Promise.prototype.inspect = function() {
						return inspect(Promise._handler(this));
					};

					return Promise;
				};

			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory(require);
		}));

	}, {
		"../state": 22
	}],
	14: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function() {

				return function generate(Promise) {

					var resolve = Promise.resolve;

					Promise.iterate = iterate;
					Promise.unfold = unfold;

					return Promise;

					/**
					 * @deprecated Use github.com/cujojs/most streams and most.iterate
					 * Generate a (potentially infinite) stream of promised values:
					 * x, f(x), f(f(x)), etc. until condition(x) returns true
					 * @param {function} f function to generate a new x from the previous x
					 * @param {function} condition function that, given the current x, returns
					 *  truthy when the iterate should stop
					 * @param {function} handler function to handle the value produced by f
					 * @param {*|Promise} x starting value, may be a promise
					 * @return {Promise} the result of the last call to f before
					 *  condition returns true
					 */
					function iterate(f, condition, handler, x) {
						return unfold(function(x) {
							return [x, f(x)];
						}, condition, handler, x);
					}

					/**
					 * @deprecated Use github.com/cujojs/most streams and most.unfold
					 * Generate a (potentially infinite) stream of promised values
					 * by applying handler(generator(seed)) iteratively until
					 * condition(seed) returns true.
					 * @param {function} unspool function that generates a [value, newSeed]
					 *  given a seed.
					 * @param {function} condition function that, given the current seed, returns
					 *  truthy when the unfold should stop
					 * @param {function} handler function to handle the value produced by unspool
					 * @param x {*|Promise} starting value, may be a promise
					 * @return {Promise} the result of the last value produced by unspool before
					 *  condition returns true
					 */
					function unfold(unspool, condition, handler, x) {
						return resolve(x).then(function(seed) {
							return resolve(condition(seed)).then(function(done) {
								return done ? seed : resolve(unspool(seed)).spread(next);
							});
						});

						function next(item, newSeed) {
							return resolve(handler(item)).then(function() {
								return unfold(unspool, condition, handler, newSeed);
							});
						}
					}
				};

			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory();
		}));

	}, {}],
	15: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function() {

				return function progress(Promise) {

					/**
					 * @deprecated
					 * Register a progress handler for this promise
					 * @param {function} onProgress
					 * @returns {Promise}
					 */
					Promise.prototype.progress = function(onProgress) {
						return this.then(void 0, void 0, onProgress);
					};

					return Promise;
				};

			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory();
		}));

	}, {}],
	16: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function(require) {

				var env = require('../env');
				var TimeoutError = require('../TimeoutError');

				function setTimeout(f, ms, x, y) {
					return env.setTimer(function() {
						f(x, y, ms);
					}, ms);
				}

				return function timed(Promise) {
					/**
					 * Return a new promise whose fulfillment value is revealed only
					 * after ms milliseconds
					 * @param {number} ms milliseconds
					 * @returns {Promise}
					 */
					Promise.prototype.delay = function(ms) {
						var p = this._beget();
						this._handler.fold(handleDelay, ms, void 0, p._handler);
						return p;
					};

					function handleDelay(ms, x, h) {
						setTimeout(resolveDelay, ms, x, h);
					}

					function resolveDelay(x, h) {
						h.resolve(x);
					}

					/**
					 * Return a new promise that rejects after ms milliseconds unless
					 * this promise fulfills earlier, in which case the returned promise
					 * fulfills with the same value.
					 * @param {number} ms milliseconds
					 * @param {Error|*=} reason optional rejection reason to use, defaults
					 *   to a TimeoutError if not provided
					 * @returns {Promise}
					 */
					Promise.prototype.timeout = function(ms, reason) {
						var p = this._beget();
						var h = p._handler;

						var t = setTimeout(onTimeout, ms, reason, p._handler);

						this._handler.visit(h,
							function onFulfill(x) {
								env.clearTimer(t);
								this.resolve(x); // this = h
							},
							function onReject(x) {
								env.clearTimer(t);
								this.reject(x); // this = h
							},
							h.notify);

						return p;
					};

					function onTimeout(reason, h, ms) {
						var e = typeof reason === 'undefined' ?
							new TimeoutError('timed out after ' + ms + 'ms') :
							reason;
						h.reject(e);
					}

					return Promise;
				};

			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory(require);
		}));

	}, {
		"../TimeoutError": 8,
		"../env": 19
	}],
	17: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function(require) {

				var setTimer = require('../env').setTimer;
				var format = require('../format');

				return function unhandledRejection(Promise) {

					var logError = noop;
					var logInfo = noop;
					var localConsole;

					if(typeof console !== 'undefined') {
						// Alias console to prevent things like uglify's drop_console option from
						// removing console.log/error. Unhandled rejections fall into the same
						// category as uncaught exceptions, and build tools shouldn't silence them.
						localConsole = console;
						logError = typeof localConsole.error !== 'undefined' ?

							function(e) {
								localConsole.error(e);
							} :
							function(e) {
								localConsole.log(e);
							};

						logInfo = typeof localConsole.info !== 'undefined' ?

							function(e) {
								localConsole.info(e);
							} :
							function(e) {
								localConsole.log(e);
							};
					}

					Promise.onPotentiallyUnhandledRejection = function(rejection) {
						enqueue(report, rejection);
					};

					Promise.onPotentiallyUnhandledRejectionHandled = function(rejection) {
						enqueue(unreport, rejection);
					};

					Promise.onFatalRejection = function(rejection) {
						enqueue(throwit, rejection.value);
					};

					var tasks = [];
					var reported = [];
					var running = null;

					function report(r) {
						if(!r.handled) {
							reported.push(r);
							logError('Potentially unhandled rejection [' + r.id + '] ' + format.formatError(r.value));
						}
					}

					function unreport(r) {
						var i = reported.indexOf(r);
						if(i >= 0) {
							reported.splice(i, 1);
							logInfo('Handled previous rejection [' + r.id + '] ' + format.formatObject(r.value));
						}
					}

					function enqueue(f, x) {
						tasks.push(f, x);
						if(running === null) {
							running = setTimer(flush, 0);
						}
					}

					function flush() {
						running = null;
						while(tasks.length > 0) {
							tasks.shift()(tasks.shift());
						}
					}

					return Promise;
				};

				function throwit(e) {
					throw e;
				}

				function noop() {}

			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory(require);
		}));

	}, {
		"../env": 19,
		"../format": 20
	}],
	18: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function() {

				return function addWith(Promise) {
					/**
					 * Returns a promise whose handlers will be called with `this` set to
					 * the supplied receiver.  Subsequent promises derived from the
					 * returned promise will also have their handlers called with receiver
					 * as `this`. Calling `with` with undefined or no arguments will return
					 * a promise whose handlers will again be called in the usual Promises/A+
					 * way (no `this`) thus safely undoing any previous `with` in the
					 * promise chain.
					 *
					 * WARNING: Promises returned from `with`/`withThis` are NOT Promises/A+
					 * compliant, specifically violating 2.2.5 (http://promisesaplus.com/#point-41)
					 *
					 * @param {object} receiver `this` value for all handlers attached to
					 *  the returned promise.
					 * @returns {Promise}
					 */
					Promise.prototype['with'] = Promise.prototype.withThis = function(receiver) {
						var p = this._beget();
						var child = p._handler;
						child.receiver = receiver;
						this._handler.chain(child, receiver);
						return p;
					};

					return Promise;
				};

			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory();
		}));

	}, {}],
	19: [function(require, module, exports) {
		(function(process) {
			/** @license MIT License (c) copyright 2010-2014 original author or authors */
			/** @author Brian Cavalier */
			/** @author John Hann */

			/*global process,document,setTimeout,clearTimeout,MutationObserver,WebKitMutationObserver*/
			(function(define) {
				'use strict';
				define(function(require) {
					/*jshint maxcomplexity:6*/

					// Sniff "best" async scheduling option
					// Prefer process.nextTick or MutationObserver, then check for
					// setTimeout, and finally vertx, since its the only env that doesn't
					// have setTimeout

					var MutationObs;
					var capturedSetTimeout = typeof setTimeout !== 'undefined' && setTimeout;

					// Default env
					var setTimer = function(f, ms) {
						return setTimeout(f, ms);
					};
					var clearTimer = function(t) {
						return clearTimeout(t);
					};
					var asap = function(f) {
						return capturedSetTimeout(f, 0);
					};

					// Detect specific env
					if(isNode()) { // Node
						asap = function(f) {
							return process.nextTick(f);
						};

					} else if(MutationObs = hasMutationObserver()) { // Modern browser
						asap = initMutationObserver(MutationObs);

					} else if(!capturedSetTimeout) { // vert.x
						var vertxRequire = require;
						var vertx = vertxRequire('vertx');
						setTimer = function(f, ms) {
							return vertx.setTimer(ms, f);
						};
						clearTimer = vertx.cancelTimer;
						asap = vertx.runOnLoop || vertx.runOnContext;
					}

					return {
						setTimer: setTimer,
						clearTimer: clearTimer,
						asap: asap
					};

					function isNode() {
						return typeof process !== 'undefined' &&
							Object.prototype.toString.call(process) === '[object process]';
					}

					function hasMutationObserver() {
						return(typeof MutationObserver === 'function' && MutationObserver) ||
							(typeof WebKitMutationObserver === 'function' && WebKitMutationObserver);
					}

					function initMutationObserver(MutationObserver) {
						var scheduled;
						var node = document.createTextNode('');
						var o = new MutationObserver(run);
						o.observe(node, {
							characterData: true
						});

						function run() {
							var f = scheduled;
							scheduled = void 0;
							f();
						}

						var i = 0;
						return function(f) {
							scheduled = f;
							node.data = (i ^= 1);
						};
					}
				});
			}(typeof define === 'function' && define.amd ? define : function(factory) {
				module.exports = factory(require);
			}));

		}).call(this, require('_process'))
	}, {
		"_process": 63
	}],
	20: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function() {

				return {
					formatError: formatError,
					formatObject: formatObject,
					tryStringify: tryStringify
				};

				/**
				 * Format an error into a string.  If e is an Error and has a stack property,
				 * it's returned.  Otherwise, e is formatted using formatObject, with a
				 * warning added about e not being a proper Error.
				 * @param {*} e
				 * @returns {String} formatted string, suitable for output to developers
				 */
				function formatError(e) {
					var s = typeof e === 'object' && e !== null && e.stack ? e.stack : formatObject(e);
					return e instanceof Error ? s : s + ' (WARNING: non-Error used)';
				}

				/**
				 * Format an object, detecting "plain" objects and running them through
				 * JSON.stringify if possible.
				 * @param {Object} o
				 * @returns {string}
				 */
				function formatObject(o) {
					var s = String(o);
					if(s === '[object Object]' && typeof JSON !== 'undefined') {
						s = tryStringify(o, s);
					}
					return s;
				}

				/**
				 * Try to return the result of JSON.stringify(x).  If that fails, return
				 * defaultValue
				 * @param {*} x
				 * @param {*} defaultValue
				 * @returns {String|*} JSON.stringify(x) or defaultValue
				 */
				function tryStringify(x, defaultValue) {
					try {
						return JSON.stringify(x);
					} catch(e) {
						return defaultValue;
					}
				}

			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory();
		}));

	}, {}],
	21: [function(require, module, exports) {
		(function(process) {
			/** @license MIT License (c) copyright 2010-2014 original author or authors */
			/** @author Brian Cavalier */
			/** @author John Hann */

			(function(define) {
				'use strict';
				define(function() {

					return function makePromise(environment) {

						var tasks = environment.scheduler;
						var emitRejection = initEmitRejection();

						var objectCreate = Object.create ||
							function(proto) {
								function Child() {}
								Child.prototype = proto;
								return new Child();
							};

						/**
						 * Create a promise whose fate is determined by resolver
						 * @constructor
						 * @returns {Promise} promise
						 * @name Promise
						 */
						function Promise(resolver, handler) {
							this._handler = resolver === Handler ? handler : init(resolver);
						}

						/**
						 * Run the supplied resolver
						 * @param resolver
						 * @returns {Pending}
						 */
						function init(resolver) {
							var handler = new Pending();

							try {
								resolver(promiseResolve, promiseReject, promiseNotify);
							} catch(e) {
								promiseReject(e);
							}

							return handler;

							/**
							 * Transition from pre-resolution state to post-resolution state, notifying
							 * all listeners of the ultimate fulfillment or rejection
							 * @param {*} x resolution value
							 */
							function promiseResolve(x) {
								handler.resolve(x);
							}
							/**
							 * Reject this promise with reason, which will be used verbatim
							 * @param {Error|*} reason rejection reason, strongly suggested
							 *   to be an Error type
							 */
							function promiseReject(reason) {
								handler.reject(reason);
							}

							/**
							 * @deprecated
							 * Issue a progress event, notifying all progress listeners
							 * @param {*} x progress event payload to pass to all listeners
							 */
							function promiseNotify(x) {
								handler.notify(x);
							}
						}

						// Creation

						Promise.resolve = resolve;
						Promise.reject = reject;
						Promise.never = never;

						Promise._defer = defer;
						Promise._handler = getHandler;

						/**
						 * Returns a trusted promise. If x is already a trusted promise, it is
						 * returned, otherwise returns a new trusted Promise which follows x.
						 * @param  {*} x
						 * @return {Promise} promise
						 */
						function resolve(x) {
							return isPromise(x) ? x :
								new Promise(Handler, new Async(getHandler(x)));
						}

						/**
						 * Return a reject promise with x as its reason (x is used verbatim)
						 * @param {*} x
						 * @returns {Promise} rejected promise
						 */
						function reject(x) {
							return new Promise(Handler, new Async(new Rejected(x)));
						}

						/**
						 * Return a promise that remains pending forever
						 * @returns {Promise} forever-pending promise.
						 */
						function never() {
							return foreverPendingPromise; // Should be frozen
						}

						/**
						 * Creates an internal {promise, resolver} pair
						 * @private
						 * @returns {Promise}
						 */
						function defer() {
							return new Promise(Handler, new Pending());
						}

						// Transformation and flow control

						/**
						 * Transform this promise's fulfillment value, returning a new Promise
						 * for the transformed result.  If the promise cannot be fulfilled, onRejected
						 * is called with the reason.  onProgress *may* be called with updates toward
						 * this promise's fulfillment.
						 * @param {function=} onFulfilled fulfillment handler
						 * @param {function=} onRejected rejection handler
						 * @param {function=} onProgress @deprecated progress handler
						 * @return {Promise} new promise
						 */
						Promise.prototype.then = function(onFulfilled, onRejected, onProgress) {
							var parent = this._handler;
							var state = parent.join().state();

							if((typeof onFulfilled !== 'function' && state > 0) ||
								(typeof onRejected !== 'function' && state < 0)) {
								// Short circuit: value will not change, simply share handler
								return new this.constructor(Handler, parent);
							}

							var p = this._beget();
							var child = p._handler;

							parent.chain(child, parent.receiver, onFulfilled, onRejected, onProgress);

							return p;
						};

						/**
						 * If this promise cannot be fulfilled due to an error, call onRejected to
						 * handle the error. Shortcut for .then(undefined, onRejected)
						 * @param {function?} onRejected
						 * @return {Promise}
						 */
						Promise.prototype['catch'] = function(onRejected) {
							return this.then(void 0, onRejected);
						};

						/**
						 * Creates a new, pending promise of the same type as this promise
						 * @private
						 * @returns {Promise}
						 */
						Promise.prototype._beget = function() {
							return begetFrom(this._handler, this.constructor);
						};

						function begetFrom(parent, Promise) {
							var child = new Pending(parent.receiver, parent.join().context);
							return new Promise(Handler, child);
						}

						// Array combinators

						Promise.all = all;
						Promise.race = race;
						Promise._traverse = traverse;

						/**
						 * Return a promise that will fulfill when all promises in the
						 * input array have fulfilled, or will reject when one of the
						 * promises rejects.
						 * @param {array} promises array of promises
						 * @returns {Promise} promise for array of fulfillment values
						 */
						function all(promises) {
							return traverseWith(snd, null, promises);
						}

						/**
						 * Array<Promise<X>> -> Promise<Array<f(X)>>
						 * @private
						 * @param {function} f function to apply to each promise's value
						 * @param {Array} promises array of promises
						 * @returns {Promise} promise for transformed values
						 */
						function traverse(f, promises) {
							return traverseWith(tryCatch2, f, promises);
						}

						function traverseWith(tryMap, f, promises) {
							var handler = typeof f === 'function' ? mapAt : settleAt;

							var resolver = new Pending();
							var pending = promises.length >>> 0;
							var results = new Array(pending);

							for(var i = 0, x; i < promises.length && !resolver.resolved; ++i) {
								x = promises[i];

								if(x === void 0 && !(i in promises)) {
									--pending;
									continue;
								}

								traverseAt(promises, handler, i, x, resolver);
							}

							if(pending === 0) {
								resolver.become(new Fulfilled(results));
							}

							return new Promise(Handler, resolver);

							function mapAt(i, x, resolver) {
								if(!resolver.resolved) {
									traverseAt(promises, settleAt, i, tryMap(f, x, i), resolver);
								}
							}

							function settleAt(i, x, resolver) {
								results[i] = x;
								if(--pending === 0) {
									resolver.become(new Fulfilled(results));
								}
							}
						}

						function traverseAt(promises, handler, i, x, resolver) {
							if(maybeThenable(x)) {
								var h = getHandlerMaybeThenable(x);
								var s = h.state();

								if(s === 0) {
									h.fold(handler, i, void 0, resolver);
								} else if(s > 0) {
									handler(i, h.value, resolver);
								} else {
									resolver.become(h);
									visitRemaining(promises, i + 1, h);
								}
							} else {
								handler(i, x, resolver);
							}
						}

						Promise._visitRemaining = visitRemaining;

						function visitRemaining(promises, start, handler) {
							for(var i = start; i < promises.length; ++i) {
								markAsHandled(getHandler(promises[i]), handler);
							}
						}

						function markAsHandled(h, handler) {
							if(h === handler) {
								return;
							}

							var s = h.state();
							if(s === 0) {
								h.visit(h, void 0, h._unreport);
							} else if(s < 0) {
								h._unreport();
							}
						}

						/**
						 * Fulfill-reject competitive race. Return a promise that will settle
						 * to the same state as the earliest input promise to settle.
						 *
						 * WARNING: The ES6 Promise spec requires that race()ing an empty array
						 * must return a promise that is pending forever.  This implementation
						 * returns a singleton forever-pending promise, the same singleton that is
						 * returned by Promise.never(), thus can be checked with ===
						 *
						 * @param {array} promises array of promises to race
						 * @returns {Promise} if input is non-empty, a promise that will settle
						 * to the same outcome as the earliest input promise to settle. if empty
						 * is empty, returns a promise that will never settle.
						 */
						function race(promises) {
							if(typeof promises !== 'object' || promises === null) {
								return reject(new TypeError('non-iterable passed to race()'));
							}

							// Sigh, race([]) is untestable unless we return *something*
							// that is recognizable without calling .then() on it.
							return promises.length === 0 ? never() :
								promises.length === 1 ? resolve(promises[0]) :
								runRace(promises);
						}

						function runRace(promises) {
							var resolver = new Pending();
							var i, x, h;
							for(i = 0; i < promises.length; ++i) {
								x = promises[i];
								if(x === void 0 && !(i in promises)) {
									continue;
								}

								h = getHandler(x);
								if(h.state() !== 0) {
									resolver.become(h);
									visitRemaining(promises, i + 1, h);
									break;
								} else {
									h.visit(resolver, resolver.resolve, resolver.reject);
								}
							}
							return new Promise(Handler, resolver);
						}

						// Promise internals
						// Below this, everything is @private

						/**
						 * Get an appropriate handler for x, without checking for cycles
						 * @param {*} x
						 * @returns {object} handler
						 */
						function getHandler(x) {
							if(isPromise(x)) {
								return x._handler.join();
							}
							return maybeThenable(x) ? getHandlerUntrusted(x) : new Fulfilled(x);
						}

						/**
						 * Get a handler for thenable x.
						 * NOTE: You must only call this if maybeThenable(x) == true
						 * @param {object|function|Promise} x
						 * @returns {object} handler
						 */
						function getHandlerMaybeThenable(x) {
							return isPromise(x) ? x._handler.join() : getHandlerUntrusted(x);
						}

						/**
						 * Get a handler for potentially untrusted thenable x
						 * @param {*} x
						 * @returns {object} handler
						 */
						function getHandlerUntrusted(x) {
							try {
								var untrustedThen = x.then;
								return typeof untrustedThen === 'function' ?
									new Thenable(untrustedThen, x) :
									new Fulfilled(x);
							} catch(e) {
								return new Rejected(e);
							}
						}

						/**
						 * Handler for a promise that is pending forever
						 * @constructor
						 */
						function Handler() {}

						Handler.prototype.when = Handler.prototype.become = Handler.prototype.notify // deprecated
							= Handler.prototype.fail = Handler.prototype._unreport = Handler.prototype._report = noop;

						Handler.prototype._state = 0;

						Handler.prototype.state = function() {
							return this._state;
						};

						/**
						 * Recursively collapse handler chain to find the handler
						 * nearest to the fully resolved value.
						 * @returns {object} handler nearest the fully resolved value
						 */
						Handler.prototype.join = function() {
							var h = this;
							while(h.handler !== void 0) {
								h = h.handler;
							}
							return h;
						};

						Handler.prototype.chain = function(to, receiver, fulfilled, rejected, progress) {
							this.when({
								resolver: to,
								receiver: receiver,
								fulfilled: fulfilled,
								rejected: rejected,
								progress: progress
							});
						};

						Handler.prototype.visit = function(receiver, fulfilled, rejected, progress) {
							this.chain(failIfRejected, receiver, fulfilled, rejected, progress);
						};

						Handler.prototype.fold = function(f, z, c, to) {
							this.when(new Fold(f, z, c, to));
						};

						/**
						 * Handler that invokes fail() on any handler it becomes
						 * @constructor
						 */
						function FailIfRejected() {}

						inherit(Handler, FailIfRejected);

						FailIfRejected.prototype.become = function(h) {
							h.fail();
						};

						var failIfRejected = new FailIfRejected();

						/**
						 * Handler that manages a queue of consumers waiting on a pending promise
						 * @constructor
						 */
						function Pending(receiver, inheritedContext) {
							Promise.createContext(this, inheritedContext);

							this.consumers = void 0;
							this.receiver = receiver;
							this.handler = void 0;
							this.resolved = false;
						}

						inherit(Handler, Pending);

						Pending.prototype._state = 0;

						Pending.prototype.resolve = function(x) {
							this.become(getHandler(x));
						};

						Pending.prototype.reject = function(x) {
							if(this.resolved) {
								return;
							}

							this.become(new Rejected(x));
						};

						Pending.prototype.join = function() {
							if(!this.resolved) {
								return this;
							}

							var h = this;

							while(h.handler !== void 0) {
								h = h.handler;
								if(h === this) {
									return this.handler = cycle();
								}
							}

							return h;
						};

						Pending.prototype.run = function() {
							var q = this.consumers;
							var handler = this.handler;
							this.handler = this.handler.join();
							this.consumers = void 0;

							for(var i = 0; i < q.length; ++i) {
								handler.when(q[i]);
							}
						};

						Pending.prototype.become = function(handler) {
							if(this.resolved) {
								return;
							}

							this.resolved = true;
							this.handler = handler;
							if(this.consumers !== void 0) {
								tasks.enqueue(this);
							}

							if(this.context !== void 0) {
								handler._report(this.context);
							}
						};

						Pending.prototype.when = function(continuation) {
							if(this.resolved) {
								tasks.enqueue(new ContinuationTask(continuation, this.handler));
							} else {
								if(this.consumers === void 0) {
									this.consumers = [continuation];
								} else {
									this.consumers.push(continuation);
								}
							}
						};

						/**
						 * @deprecated
						 */
						Pending.prototype.notify = function(x) {
							if(!this.resolved) {
								tasks.enqueue(new ProgressTask(x, this));
							}
						};

						Pending.prototype.fail = function(context) {
							var c = typeof context === 'undefined' ? this.context : context;
							this.resolved && this.handler.join().fail(c);
						};

						Pending.prototype._report = function(context) {
							this.resolved && this.handler.join()._report(context);
						};

						Pending.prototype._unreport = function() {
							this.resolved && this.handler.join()._unreport();
						};

						/**
						 * Wrap another handler and force it into a future stack
						 * @param {object} handler
						 * @constructor
						 */
						function Async(handler) {
							this.handler = handler;
						}

						inherit(Handler, Async);

						Async.prototype.when = function(continuation) {
							tasks.enqueue(new ContinuationTask(continuation, this));
						};

						Async.prototype._report = function(context) {
							this.join()._report(context);
						};

						Async.prototype._unreport = function() {
							this.join()._unreport();
						};

						/**
						 * Handler that wraps an untrusted thenable and assimilates it in a future stack
						 * @param {function} then
						 * @param {{then: function}} thenable
						 * @constructor
						 */
						function Thenable(then, thenable) {
							Pending.call(this);
							tasks.enqueue(new AssimilateTask(then, thenable, this));
						}

						inherit(Pending, Thenable);

						/**
						 * Handler for a fulfilled promise
						 * @param {*} x fulfillment value
						 * @constructor
						 */
						function Fulfilled(x) {
							Promise.createContext(this);
							this.value = x;
						}

						inherit(Handler, Fulfilled);

						Fulfilled.prototype._state = 1;

						Fulfilled.prototype.fold = function(f, z, c, to) {
							runContinuation3(f, z, this, c, to);
						};

						Fulfilled.prototype.when = function(cont) {
							runContinuation1(cont.fulfilled, this, cont.receiver, cont.resolver);
						};

						var errorId = 0;

						/**
						 * Handler for a rejected promise
						 * @param {*} x rejection reason
						 * @constructor
						 */
						function Rejected(x) {
							Promise.createContext(this);

							this.id = ++errorId;
							this.value = x;
							this.handled = false;
							this.reported = false;

							this._report();
						}

						inherit(Handler, Rejected);

						Rejected.prototype._state = -1;

						Rejected.prototype.fold = function(f, z, c, to) {
							to.become(this);
						};

						Rejected.prototype.when = function(cont) {
							if(typeof cont.rejected === 'function') {
								this._unreport();
							}
							runContinuation1(cont.rejected, this, cont.receiver, cont.resolver);
						};

						Rejected.prototype._report = function(context) {
							tasks.afterQueue(new ReportTask(this, context));
						};

						Rejected.prototype._unreport = function() {
							if(this.handled) {
								return;
							}
							this.handled = true;
							tasks.afterQueue(new UnreportTask(this));
						};

						Rejected.prototype.fail = function(context) {
							this.reported = true;
							emitRejection('unhandledRejection', this);
							Promise.onFatalRejection(this, context === void 0 ? this.context : context);
						};

						function ReportTask(rejection, context) {
							this.rejection = rejection;
							this.context = context;
						}

						ReportTask.prototype.run = function() {
							if(!this.rejection.handled && !this.rejection.reported) {
								this.rejection.reported = true;
								emitRejection('unhandledRejection', this.rejection) ||
									Promise.onPotentiallyUnhandledRejection(this.rejection, this.context);
							}
						};

						function UnreportTask(rejection) {
							this.rejection = rejection;
						}

						UnreportTask.prototype.run = function() {
							if(this.rejection.reported) {
								emitRejection('rejectionHandled', this.rejection) ||
									Promise.onPotentiallyUnhandledRejectionHandled(this.rejection);
							}
						};

						// Unhandled rejection hooks
						// By default, everything is a noop

						Promise.createContext = Promise.enterContext = Promise.exitContext = Promise.onPotentiallyUnhandledRejection = Promise.onPotentiallyUnhandledRejectionHandled = Promise.onFatalRejection = noop;

						// Errors and singletons

						var foreverPendingHandler = new Handler();
						var foreverPendingPromise = new Promise(Handler, foreverPendingHandler);

						function cycle() {
							return new Rejected(new TypeError('Promise cycle'));
						}

						// Task runners

						/**
						 * Run a single consumer
						 * @constructor
						 */
						function ContinuationTask(continuation, handler) {
							this.continuation = continuation;
							this.handler = handler;
						}

						ContinuationTask.prototype.run = function() {
							this.handler.join().when(this.continuation);
						};

						/**
						 * Run a queue of progress handlers
						 * @constructor
						 */
						function ProgressTask(value, handler) {
							this.handler = handler;
							this.value = value;
						}

						ProgressTask.prototype.run = function() {
							var q = this.handler.consumers;
							if(q === void 0) {
								return;
							}

							for(var c, i = 0; i < q.length; ++i) {
								c = q[i];
								runNotify(c.progress, this.value, this.handler, c.receiver, c.resolver);
							}
						};

						/**
						 * Assimilate a thenable, sending it's value to resolver
						 * @param {function} then
						 * @param {object|function} thenable
						 * @param {object} resolver
						 * @constructor
						 */
						function AssimilateTask(then, thenable, resolver) {
							this._then = then;
							this.thenable = thenable;
							this.resolver = resolver;
						}

						AssimilateTask.prototype.run = function() {
							var h = this.resolver;
							tryAssimilate(this._then, this.thenable, _resolve, _reject, _notify);

							function _resolve(x) {
								h.resolve(x);
							}

							function _reject(x) {
								h.reject(x);
							}

							function _notify(x) {
								h.notify(x);
							}
						};

						function tryAssimilate(then, thenable, resolve, reject, notify) {
							try {
								then.call(thenable, resolve, reject, notify);
							} catch(e) {
								reject(e);
							}
						}

						/**
						 * Fold a handler value with z
						 * @constructor
						 */
						function Fold(f, z, c, to) {
							this.f = f;
							this.z = z;
							this.c = c;
							this.to = to;
							this.resolver = failIfRejected;
							this.receiver = this;
						}

						Fold.prototype.fulfilled = function(x) {
							this.f.call(this.c, this.z, x, this.to);
						};

						Fold.prototype.rejected = function(x) {
							this.to.reject(x);
						};

						Fold.prototype.progress = function(x) {
							this.to.notify(x);
						};

						// Other helpers

						/**
						 * @param {*} x
						 * @returns {boolean} true iff x is a trusted Promise
						 */
						function isPromise(x) {
							return x instanceof Promise;
						}

						/**
						 * Test just enough to rule out primitives, in order to take faster
						 * paths in some code
						 * @param {*} x
						 * @returns {boolean} false iff x is guaranteed *not* to be a thenable
						 */
						function maybeThenable(x) {
							return(typeof x === 'object' || typeof x === 'function') && x !== null;
						}

						function runContinuation1(f, h, receiver, next) {
							if(typeof f !== 'function') {
								return next.become(h);
							}

							Promise.enterContext(h);
							tryCatchReject(f, h.value, receiver, next);
							Promise.exitContext();
						}

						function runContinuation3(f, x, h, receiver, next) {
							if(typeof f !== 'function') {
								return next.become(h);
							}

							Promise.enterContext(h);
							tryCatchReject3(f, x, h.value, receiver, next);
							Promise.exitContext();
						}

						/**
						 * @deprecated
						 */
						function runNotify(f, x, h, receiver, next) {
							if(typeof f !== 'function') {
								return next.notify(x);
							}

							Promise.enterContext(h);
							tryCatchReturn(f, x, receiver, next);
							Promise.exitContext();
						}

						function tryCatch2(f, a, b) {
							try {
								return f(a, b);
							} catch(e) {
								return reject(e);
							}
						}

						/**
						 * Return f.call(thisArg, x), or if it throws return a rejected promise for
						 * the thrown exception
						 */
						function tryCatchReject(f, x, thisArg, next) {
							try {
								next.become(getHandler(f.call(thisArg, x)));
							} catch(e) {
								next.become(new Rejected(e));
							}
						}

						/**
						 * Same as above, but includes the extra argument parameter.
						 */
						function tryCatchReject3(f, x, y, thisArg, next) {
							try {
								f.call(thisArg, x, y, next);
							} catch(e) {
								next.become(new Rejected(e));
							}
						}

						/**
						 * @deprecated
						 * Return f.call(thisArg, x), or if it throws, *return* the exception
						 */
						function tryCatchReturn(f, x, thisArg, next) {
							try {
								next.notify(f.call(thisArg, x));
							} catch(e) {
								next.notify(e);
							}
						}

						function inherit(Parent, Child) {
							Child.prototype = objectCreate(Parent.prototype);
							Child.prototype.constructor = Child;
						}

						function snd(x, y) {
							return y;
						}

						function noop() {}

						function initEmitRejection() {
							/*global process, self, CustomEvent*/
							if(typeof process !== 'undefined' && process !== null &&
								typeof process.emit === 'function') {
								// Returning falsy here means to call the default
								// onPotentiallyUnhandledRejection API.  This is safe even in
								// browserify since process.emit always returns falsy in browserify:
								// https://github.com/defunctzombie/node-process/blob/master/browser.js#L40-L46
								return function(type, rejection) {
									return type === 'unhandledRejection' ?
										process.emit(type, rejection.value, rejection) :
										process.emit(type, rejection);
								};
							} else if(typeof self !== 'undefined' && typeof CustomEvent === 'function') {
								return(function(noop, self, CustomEvent) {
									var hasCustomEvent = false;
									try {
										var ev = new CustomEvent('unhandledRejection');
										hasCustomEvent = ev instanceof CustomEvent;
									} catch(e) {}

									return !hasCustomEvent ? noop : function(type, rejection) {
										var ev = new CustomEvent(type, {
											detail: {
												reason: rejection.value,
												key: rejection
											},
											bubbles: false,
											cancelable: true
										});

										return !self.dispatchEvent(ev);
									};
								}(noop, self, CustomEvent));
							}

							return noop;
						}

						return Promise;
					};
				});
			}(typeof define === 'function' && define.amd ? define : function(factory) {
				module.exports = factory();
			}));

		}).call(this, require('_process'))
	}, {
		"_process": 63
	}],
	22: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */
		/** @author Brian Cavalier */
		/** @author John Hann */

		(function(define) {
			'use strict';
			define(function() {

				return {
					pending: toPendingState,
					fulfilled: toFulfilledState,
					rejected: toRejectedState,
					inspect: inspect
				};

				function toPendingState() {
					return {
						state: 'pending'
					};
				}

				function toRejectedState(e) {
					return {
						state: 'rejected',
						reason: e
					};
				}

				function toFulfilledState(x) {
					return {
						state: 'fulfilled',
						value: x
					};
				}

				function inspect(handler) {
					var state = handler.state();
					return state === 0 ? toPendingState() :
						state > 0 ? toFulfilledState(handler.value) :
						toRejectedState(handler.value);
				}

			});
		}(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory();
		}));

	}, {}],
	23: [function(require, module, exports) {
		/** @license MIT License (c) copyright 2010-2014 original author or authors */

		/**
		 * Promises/A+ and when() implementation
		 * when is part of the cujoJS family of libraries (http://cujojs.com/)
		 * @author Brian Cavalier
		 * @author John Hann
		 */
		(function(define) {
			'use strict';
			define(function(require) {

				var timed = require('./lib/decorators/timed');
				var array = require('./lib/decorators/array');
				var flow = require('./lib/decorators/flow');
				var fold = require('./lib/decorators/fold');
				var inspect = require('./lib/decorators/inspect');
				var generate = require('./lib/decorators/iterate');
				var progress = require('./lib/decorators/progress');
				var withThis = require('./lib/decorators/with');
				var unhandledRejection = require('./lib/decorators/unhandledRejection');
				var TimeoutError = require('./lib/TimeoutError');

				var Promise = [array, flow, fold, generate, progress,
						inspect, withThis, timed, unhandledRejection
					]
					.reduce(function(Promise, feature) {
						return feature(Promise);
					}, require('./lib/Promise'));

				var apply = require('./lib/apply')(Promise);

				// Public API

				when.promise = promise; // Create a pending promise
				when.resolve = Promise.resolve; // Create a resolved promise
				when.reject = Promise.reject; // Create a rejected promise

				when.lift = lift; // lift a function to return promises
				when['try'] = attempt; // call a function and return a promise
				when.attempt = attempt; // alias for when.try

				when.iterate = Promise.iterate; // DEPRECATED (use cujojs/most streams) Generate a stream of promises
				when.unfold = Promise.unfold; // DEPRECATED (use cujojs/most streams) Generate a stream of promises

				when.join = join; // Join 2 or more promises

				when.all = all; // Resolve a list of promises
				when.settle = settle; // Settle a list of promises

				when.any = lift(Promise.any); // One-winner race
				when.some = lift(Promise.some); // Multi-winner race
				when.race = lift(Promise.race); // First-to-settle race

				when.map = map; // Array.map() for promises
				when.filter = filter; // Array.filter() for promises
				when.reduce = lift(Promise.reduce); // Array.reduce() for promises
				when.reduceRight = lift(Promise.reduceRight); // Array.reduceRight() for promises

				when.isPromiseLike = isPromiseLike; // Is something promise-like, aka thenable

				when.Promise = Promise; // Promise constructor
				when.defer = defer; // Create a {promise, resolve, reject} tuple

				// Error types

				when.TimeoutError = TimeoutError;

				/**
				 * Get a trusted promise for x, or by transforming x with onFulfilled
				 *
				 * @param {*} x
				 * @param {function?} onFulfilled callback to be called when x is
				 *   successfully fulfilled.  If promiseOrValue is an immediate value, callback
				 *   will be invoked immediately.
				 * @param {function?} onRejected callback to be called when x is
				 *   rejected.
				 * @param {function?} onProgress callback to be called when progress updates
				 *   are issued for x. @deprecated
				 * @returns {Promise} a new promise that will fulfill with the return
				 *   value of callback or errback or the completion value of promiseOrValue if
				 *   callback and/or errback is not supplied.
				 */
				function when(x, onFulfilled, onRejected, onProgress) {
					var p = Promise.resolve(x);
					if(arguments.length < 2) {
						return p;
					}

					return p.then(onFulfilled, onRejected, onProgress);
				}

				/**
				 * Creates a new promise whose fate is determined by resolver.
				 * @param {function} resolver function(resolve, reject, notify)
				 * @returns {Promise} promise whose fate is determine by resolver
				 */
				function promise(resolver) {
					return new Promise(resolver);
				}

				/**
				 * Lift the supplied function, creating a version of f that returns
				 * promises, and accepts promises as arguments.
				 * @param {function} f
				 * @returns {Function} version of f that returns promises
				 */
				function lift(f) {
					return function() {
						for(var i = 0, l = arguments.length, a = new Array(l); i < l; ++i) {
							a[i] = arguments[i];
						}
						return apply(f, this, a);
					};
				}

				/**
				 * Call f in a future turn, with the supplied args, and return a promise
				 * for the result.
				 * @param {function} f
				 * @returns {Promise}
				 */
				function attempt(f /*, args... */ ) {
					/*jshint validthis:true */
					for(var i = 0, l = arguments.length - 1, a = new Array(l); i < l; ++i) {
						a[i] = arguments[i + 1];
					}
					return apply(f, this, a);
				}

				/**
				 * Creates a {promise, resolver} pair, either or both of which
				 * may be given out safely to consumers.
				 * @return {{promise: Promise, resolve: function, reject: function, notify: function}}
				 */
				function defer() {
					return new Deferred();
				}

				function Deferred() {
					var p = Promise._defer();

					function resolve(x) {
						p._handler.resolve(x);
					}

					function reject(x) {
						p._handler.reject(x);
					}

					function notify(x) {
						p._handler.notify(x);
					}

					this.promise = p;
					this.resolve = resolve;
					this.reject = reject;
					this.notify = notify;
					this.resolver = {
						resolve: resolve,
						reject: reject,
						notify: notify
					};
				}

				/**
				 * Determines if x is promise-like, i.e. a thenable object
				 * NOTE: Will return true for *any thenable object*, and isn't truly
				 * safe, since it may attempt to access the `then` property of x (i.e.
				 *  clever/malicious getters may do weird things)
				 * @param {*} x anything
				 * @returns {boolean} true if x is promise-like
				 */
				function isPromiseLike(x) {
					return x && typeof x.then === 'function';
				}

				/**
				 * Return a promise that will resolve only once all the supplied arguments
				 * have resolved. The resolution value of the returned promise will be an array
				 * containing the resolution values of each of the arguments.
				 * @param {...*} arguments may be a mix of promises and values
				 * @returns {Promise}
				 */
				function join( /* ...promises */ ) {
					return Promise.all(arguments);
				}

				/**
				 * Return a promise that will fulfill once all input promises have
				 * fulfilled, or reject when any one input promise rejects.
				 * @param {array|Promise} promises array (or promise for an array) of promises
				 * @returns {Promise}
				 */
				function all(promises) {
					return when(promises, Promise.all);
				}

				/**
				 * Return a promise that will always fulfill with an array containing
				 * the outcome states of all input promises.  The returned promise
				 * will only reject if `promises` itself is a rejected promise.
				 * @param {array|Promise} promises array (or promise for an array) of promises
				 * @returns {Promise} promise for array of settled state descriptors
				 */
				function settle(promises) {
					return when(promises, Promise.settle);
				}

				/**
				 * Promise-aware array map function, similar to `Array.prototype.map()`,
				 * but input array may contain promises or values.
				 * @param {Array|Promise} promises array of anything, may contain promises and values
				 * @param {function(x:*, index:Number):*} mapFunc map function which may
				 *  return a promise or value
				 * @returns {Promise} promise that will fulfill with an array of mapped values
				 *  or reject if any input promise rejects.
				 */
				function map(promises, mapFunc) {
					return when(promises, function(promises) {
						return Promise.map(promises, mapFunc);
					});
				}

				/**
				 * Filter the provided array of promises using the provided predicate.  Input may
				 * contain promises and values
				 * @param {Array|Promise} promises array of promises and values
				 * @param {function(x:*, index:Number):boolean} predicate filtering predicate.
				 *  Must return truthy (or promise for truthy) for items to retain.
				 * @returns {Promise} promise that will fulfill with an array containing all items
				 *  for which predicate returned truthy.
				 */
				function filter(promises, predicate) {
					return when(promises, function(promises) {
						return Promise.filter(promises, predicate);
					});
				}

				return when;
			});
		})(typeof define === 'function' && define.amd ? define : function(factory) {
			module.exports = factory(require);
		});

	}, {
		"./lib/Promise": 6,
		"./lib/TimeoutError": 8,
		"./lib/apply": 9,
		"./lib/decorators/array": 10,
		"./lib/decorators/flow": 11,
		"./lib/decorators/fold": 12,
		"./lib/decorators/inspect": 13,
		"./lib/decorators/iterate": 14,
		"./lib/decorators/progress": 15,
		"./lib/decorators/timed": 16,
		"./lib/decorators/unhandledRejection": 17,
		"./lib/decorators/with": 18
	}],
	24: [function(require, module, exports) {
		"use strict";

		module.exports = function() {

			var d3 = require("d3");

			var blacklist = [
				/xtremeweatherforecast/i
			];

			return {
				contains: function(referrer) {
					var host = referrer.split("/")[2] || "";
					return blacklist.some(function(regex) {
						return regex.test(host);
					});
				},
				deny: function() {
					return d3.select("body")
						.attr("style", "font-size: 12em; color: red;")
						.text("XX - contact @cambecc")
						.selectAll("*")
						.remove();
				},
			};

		}();

	}, {
		"d3": 3
	}],
	25: [function(require, module, exports) {
		"use strict";

		/*
		 * clock: a singleton wall clock that can be calibrated against the server or explicitly set.
		 *
		 *   var clock = require("./clock");                                    // uncalibrated clock using system time
		 *   var clock = require("./clock").calibration({fixed: new Date()});   // clock set to a fixed time
		 *   var clock = require("./clock").calibration({server: "/"});         // clock synchronized with server at url
		 *
		 *   var calibration = clock.calibration();                             // current calibration
		 *   clock.calibrated().then(calibration => console.log(calibration));  // promise for pending calibration
		 *
		 *   clock.now();  // current time according to clock, in millis.
		 *
		 * Example calibrations:
		 *   {skew: 0}                   // uncalibrated
		 *   {fixed: 1448172991313}      // fixed
		 *   {skew: 0, δ: 200, θ: 300}   // server calibrated
		 */

		var when = require("when");

		var calibration = {
				skew: 0
			},
			p = when.resolve(calibration);

		function skewClock() {
			return Date.now() + calibration.skew;
		}

		function fixedClock() {
			return calibration.fixed;
		}

		var clock = module.exports = {
			/** @returns {number} unix time */
			now: function() {
				return skewClock();
			},
			/**
			 * @param {Object?} c sets the calibration: {server: url} or {fixed: date}. When server specified, an XHR fetches
			 *        the server time. The `calibrated` method returns this operation's promise. Example server url: "/"
			 * @returns {Object} current calibration {skew: number} or {fixed: number}, or `this` when setting calibration.
			 */
			calibration: function(c) {
				if(c === undefined) {
					return calibration;
				}
				if(typeof c.server === "string") {
					p = calibrate(c.server).then(function(c) {
						clock.now = skewClock;
						return calibration = c;
					});
				} else {
					var fixed = +new Date(c.fixed);
					if(fixed === fixed) {
						clock.now = fixedClock;
						p = when.resolve(calibration = {
							fixed: fixed
						});
					} else {
						clock.now = skewClock;
						p = when.resolve(calibration = {
							skew: +c.skew || 0
						});
					}
				}
				return this;
			},
			/** @returns {Promise} a promise for the most recently set calibration. */
			calibrated: function() {
				return p;
			},
		};

		function calibrate(url) {

			// Calculate offset θ by measuring round-trip time δ of an HTTPS request against the "Date" header of the
			// response (i.e., the server's time). See https://en.wikipedia.org/wiki/Network_Time_Protocol.
			//
			// server      t1--t2
			//            /      \
			// client   t0        t3
			//
			// The precision of t0 and t3 is in milliseconds whereas t1 and t2 is in seconds. But we don't care because
			// we don't need sub-second accuracy.

			var d = when.defer();
			var req = new XMLHttpRequest(),
				t0 = Date.now();
			req.onload = function() {
				var t3 = Date.now(),
					t2 = new Date(this.getResponseHeader("Date")).getTime() || NaN,
					t1 = t2;
				var δ = (t3 - t0) - (t2 - t1);
				var θ = ((t1 - t0) + (t2 - t3)) / 2;
				// Use offset when it is larger than 10 sec and larger than round-trip by an order of magnitude. Seems legit.
				var skew = Math.abs(θ) > Math.max(10000, δ * 10) ? θ : 0;
				d.resolve({
					skew: skew,
					δ: δ,
					θ: θ
				});
			};
			req.onerror = function(e) {
				d.reject(e);
			};
			req.open("HEAD", url);
			req.setRequestHeader("Cache-Control", "no-cache");
			req.send();
			return d.promise;
		}

	}, {
		"when": 23
	}],
	26: [function(require, module, exports) {
		module.exports = function(d3) {

			var ε = 1e-6,
				π = Math.PI,
				radians = π / 180,
				degrees = 180 / π;

			// Creates a polyhedral projection.
			//  * root: a spanning tree of polygon faces.  Nodes are automatically
			//    augmented with a transform matrix.
			//  * face: a function that returns the appropriate node for a given {λ, φ}
			//    point (radians).
			//  * r: rotation angle for final polyhedron net.  Defaults to -π / 6 (for
			//    butterflies).
			d3.geo.polyhedron = function(root, face, r) {

				r = r == null ? -π / 6 : r; // TODO automate

				recurse(root, {
					transform: [
						Math.cos(r), Math.sin(r), 0, -Math.sin(r), Math.cos(r), 0
					]
				});

				function recurse(node, parent) {
					node.edges = faceEdges(node.face);
					if(parent) {
						// Find shared edge.
						if(parent.face) {
							var shared = node.shared = sharedEdge(node.face, parent.face),
								m = matrix(shared.map(parent.project), shared.map(node.project));
							node.transform = parent.transform ? multiply(parent.transform, m) : m;
							// Replace shared edge in parent edges array.
							var edges = parent.edges;
							for(var i = 0, n = edges.length; i < n; ++i) {
								if(pointEqual(shared[0], edges[i][1]) && pointEqual(shared[1], edges[i][0])) edges[i] = node;
								if(pointEqual(shared[0], edges[i][0]) && pointEqual(shared[1], edges[i][1])) edges[i] = node;
							}
							var edges = node.edges;
							for(var i = 0, n = edges.length; i < n; ++i) {
								if(pointEqual(shared[0], edges[i][0]) && pointEqual(shared[1], edges[i][1])) edges[i] = parent;
								if(pointEqual(shared[0], edges[i][1]) && pointEqual(shared[1], edges[i][0])) edges[i] = parent;
							}
						} else {
							node.transform = parent.transform;
						}
					}
					if(node.children) {
						node.children.forEach(function(child) {
							recurse(child, node);
						});
					}
					return node;
				}

				function forward(λ, φ) {
					var node = face(λ, φ),
						point = node.project([λ * degrees, φ * degrees]),
						t;
					if(t = node.transform) {
						return [
							t[0] * point[0] + t[1] * point[1] + t[2], -(t[3] * point[0] + t[4] * point[1] + t[5])
						];
					}
					point[1] = -point[1];
					return point;
				}

				// Naive inverse!  A faster solution would use bounding boxes, or even a
				// polygonal quadtree.
				if(hasInverse(root)) forward.invert = function(x, y) {
					var coordinates = faceInvert(root, [x, -y]);
					return coordinates && (coordinates[0] *= radians, coordinates[1] *= radians, coordinates);
				};

				function faceInvert(node, coordinates) {
					var invert = node.project.invert,
						t = node.transform,
						point = coordinates;
					if(t) {
						t = inverseTransform(t);
						point = [
							t[0] * point[0] + t[1] * point[1] + t[2],
							(t[3] * point[0] + t[4] * point[1] + t[5])
						];
					}
					if(invert && node === faceDegrees(p = invert(point))) return p;
					var p,
						children = node.children;
					for(var i = 0, n = children && children.length; i < n; ++i) {
						if(p = faceInvert(children[i], coordinates)) return p;
					}
				}

				function faceDegrees(coordinates) {
					return face(coordinates[0] * radians, coordinates[1] * radians);
				}

				var projection = d3.geo.projection(forward),
					stream_ = projection.stream;

				projection.stream = function(stream) {
					var rotate = projection.rotate(),
						rotateStream = stream_(stream),
						sphereStream = (projection.rotate([0, 0]), stream_(stream));
					projection.rotate(rotate);
					rotateStream.sphere = function() {
						sphereStream.polygonStart();
						sphereStream.lineStart();
						outline(sphereStream, root);
						sphereStream.lineEnd();
						sphereStream.polygonEnd();
					};
					return rotateStream;
				};

				return projection;
			};

			d3.geo.polyhedron.butterfly = function(faceProjection) {

				faceProjection = faceProjection || function(face) {
					var centroid = d3.geo.centroid({
						type: "MultiPoint",
						coordinates: face
					});
					return d3.geo.gnomonic().scale(1).translate([0, 0]).rotate([-centroid[0], -centroid[1]]);
				};

				var faces = d3.geo.polyhedron.octahedron.map(function(face) {
					return {
						face: face,
						project: faceProjection(face)
					};
				});

				[-1, 0, 0, 1, 0, 1, 4, 5].forEach(function(d, i) {
					var node = faces[d];
					node && (node.children || (node.children = [])).push(faces[i]);
				});

				return d3.geo.polyhedron(faces[0], function(λ, φ) {
					return faces[
						λ < -π / 2 ? φ < 0 ? 6 : 4 :
						λ < 0 ? φ < 0 ? 2 : 0 :
						λ < π / 2 ? φ < 0 ? 3 : 1 :
						φ < 0 ? 7 : 5];
				});
			};

			d3.geo.polyhedron.waterman = function(faceProjection) {

				faceProjection = faceProjection || function(face) {
					var centroid = face.length === 6 ? d3.geo.centroid({
						type: "MultiPoint",
						coordinates: face
					}) : face[0];
					return d3.geo.gnomonic().scale(1).translate([0, 0]).rotate([-centroid[0], -centroid[1]]);
				};

				var octahedron = d3.geo.polyhedron.octahedron;

				var w5 = octahedron.map(function(face) {
					var xyz = face.map(cartesian),
						n = xyz.length,
						a = xyz[n - 1],
						b,
						hexagon = [];
					for(var i = 0; i < n; ++i) {
						b = xyz[i];
						hexagon.push(spherical([
							a[0] * 0.9486832980505138 + b[0] * 0.31622776601683794,
							a[1] * 0.9486832980505138 + b[1] * 0.31622776601683794,
							a[2] * 0.9486832980505138 + b[2] * 0.31622776601683794
						]), spherical([
							b[0] * 0.9486832980505138 + a[0] * 0.31622776601683794,
							b[1] * 0.9486832980505138 + a[1] * 0.31622776601683794,
							b[2] * 0.9486832980505138 + a[2] * 0.31622776601683794
						]));
						a = b;
					}
					return hexagon;
				});

				var cornerNormals = [];

				var parents = [-1, 0, 0, 1, 0, 1, 4, 5];

				w5.forEach(function(hexagon, j) {
					var face = octahedron[j],
						n = face.length,
						normals = cornerNormals[j] = [];
					for(var i = 0; i < n; ++i) {
						w5.push([
							face[i],
							hexagon[(i * 2 + 2) % (2 * n)],
							hexagon[(i * 2 + 1) % (2 * n)]
						]);
						parents.push(j);
						normals.push(cross(
							cartesian(hexagon[(i * 2 + 2) % (2 * n)]),
							cartesian(hexagon[(i * 2 + 1) % (2 * n)])
						));
					}
				});

				var faces = w5.map(function(face) {
					return {
						project: faceProjection(face),
						face: face
					};
				});

				parents.forEach(function(d, i) {
					var parent = faces[d];
					parent && (parent.children || (parent.children = [])).push(faces[i]);
				});

				return d3.geo.polyhedron(faces[0], face).center([0, 45]);

				function face(λ, φ) {
					var cosφ = Math.cos(φ),
						p = [cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ)];

					var hexagon = λ < -π / 2 ? φ < 0 ? 6 : 4 :
						λ < 0 ? φ < 0 ? 2 : 0 :
						λ < π / 2 ? φ < 0 ? 3 : 1 :
						φ < 0 ? 7 : 5;

					var n = cornerNormals[hexagon];

					return faces[
						dot(n[0], p) < 0 ? 8 + 3 * hexagon :
						dot(n[1], p) < 0 ? 8 + 3 * hexagon + 1 :
						dot(n[2], p) < 0 ? 8 + 3 * hexagon + 2 :
						hexagon];
				}
			};

			function outline(stream, node, parent) {
				var point,
					edges = node.edges,
					n = edges.length,
					edge,
					multiPoint = {
						type: "MultiPoint",
						coordinates: node.face
					},
					notPoles = node.face.filter(function(d) {
						return Math.abs(d[1]) !== 90;
					}),
					bounds = d3.geo.bounds({
						type: "MultiPoint",
						coordinates: notPoles
					}),
					inside = false,
					j = -1,
					dx = bounds[1][0] - bounds[0][0];
				// TODO
				var centroid = dx === 180 || dx === 360 ?
					[(bounds[0][0] + bounds[1][0]) / 2, (bounds[0][1] + bounds[1][1]) / 2] :
					d3.geo.centroid(multiPoint);
				// First find the shared edge…
				if(parent)
					while(++j < n) {
						if(edges[j] === parent) break;
					}
					++j;
				for(var i = 0; i < n; ++i) {
					edge = edges[(i + j) % n];
					if(Array.isArray(edge)) {
						if(!inside) {
							stream.point((point = d3.geo.interpolate(edge[0], centroid)(ε))[0], point[1]);
							inside = true;
						}
						stream.point((point = d3.geo.interpolate(edge[1], centroid)(ε))[0], point[1]);
					} else {
						inside = false;
						if(edge !== parent) outline(stream, edge, node);
					}
				}
			}

			// TODO generate on-the-fly to avoid external modification.
			var octahedron = [
				[0, 90],
				[-90, 0],
				[0, 0],
				[90, 0],
				[180, 0],
				[0, -90]
			];

			d3.geo.polyhedron.octahedron = [
				[0, 2, 1],
				[0, 3, 2],
				[5, 1, 2],
				[5, 2, 3],
				[0, 1, 4],
				[0, 4, 3],
				[5, 4, 1],
				[5, 3, 4]
			].map(function(face) {
				return face.map(function(i) {
					return octahedron[i];
				});
			});

			var φ1 = Math.atan(Math.SQRT1_2) * degrees;

			var cube = [
				[0, φ1],
				[90, φ1],
				[180, φ1],
				[-90, φ1],
				[0, -φ1],
				[90, -φ1],
				[180, -φ1],
				[-90, -φ1]
			];

			d3.geo.polyhedron.cube = [
				[0, 3, 2, 1], // N
				[0, 1, 5, 4],
				[1, 2, 6, 5],
				[2, 3, 7, 6],
				[3, 0, 4, 7],
				[4, 5, 6, 7] // S
			].map(function(face) {
				return face.map(function(i) {
					return cube[i];
				});
			});

			// Finds a shared edge given two clockwise polygons.
			function sharedEdge(a, b) {
				var x, y, n = a.length,
					found = null;
				for(var i = 0; i < n; ++i) {
					x = a[i];
					for(var j = b.length; --j >= 0;) {
						y = b[j];
						if(x[0] === y[0] && x[1] === y[1]) {
							if(found) return [found, x];
							found = x;
						}
					}
				}
			}

			// Note: 6-element arrays are used to denote the 3x3 affine transform matrix:
			// [a, b, c,
			//  d, e, f,
			//  0, 0, 1] - this redundant row is left out.

			// Transform matrix for [a0, a1] -> [b0, b1].
			function matrix(a, b) {
				var u = subtract(a[1], a[0]),
					v = subtract(b[1], b[0]),
					φ = angle(u, v),
					s = length(u) / length(v);

				return multiply([
					1, 0, a[0][0],
					0, 1, a[0][1]
				], multiply([
					s, 0, 0,
					0, s, 0
				], multiply([
					Math.cos(φ), Math.sin(φ), 0, -Math.sin(φ), Math.cos(φ), 0
				], [
					1, 0, -b[0][0],
					0, 1, -b[0][1]
				])));
			}

			// Inverts a transform matrix.
			function inverseTransform(m) {
				var k = 1 / (m[0] * m[4] - m[1] * m[3]);
				return [
					k * m[4], -k * m[1], k * (m[1] * m[5] - m[2] * m[4]), -k * m[3], k * m[0], k * (m[2] * m[3] - m[0] * m[5])
				];
			}

			// Multiplies two 3x2 matrices.
			function multiply(a, b) {
				return [
					a[0] * b[0] + a[1] * b[3],
					a[0] * b[1] + a[1] * b[4],
					a[0] * b[2] + a[1] * b[5] + a[2],
					a[3] * b[0] + a[4] * b[3],
					a[3] * b[1] + a[4] * b[4],
					a[3] * b[2] + a[4] * b[5] + a[5]
				];
			}

			// Subtracts 2D vectors.
			function subtract(a, b) {
				return [a[0] - b[0], a[1] - b[1]];
			}

			// Magnitude of a 2D vector.
			function length(v) {
				return Math.sqrt(v[0] * v[0] + v[1] * v[1]);
			}

			// Angle between two 2D vectors.
			function angle(a, b) {
				return Math.atan2(a[0] * b[1] - a[1] * b[0], a[0] * b[0] + a[1] * b[1]);
			}

			function dot(a, b) {
				for(var i = 0, n = a.length, s = 0; i < n; ++i) s += a[i] * b[i];
				return s;
			}

			function cross(a, b) {
				return [
					a[1] * b[2] - a[2] * b[1],
					a[2] * b[0] - a[0] * b[2],
					a[0] * b[1] - a[1] * b[0]
				];
			}

			// Converts 3D Cartesian to spherical coordinates (degrees).
			function spherical(cartesian) {
				return [
					Math.atan2(cartesian[1], cartesian[0]) * degrees,
					Math.asin(Math.max(-1, Math.min(1, cartesian[2]))) * degrees
				];
			}

			// Converts spherical coordinates (degrees) to 3D Cartesian.
			function cartesian(coordinates) {
				var λ = coordinates[0] * radians,
					φ = coordinates[1] * radians,
					cosφ = Math.cos(φ);
				return [
					cosφ * Math.cos(λ),
					cosφ * Math.sin(λ),
					Math.sin(φ)
				];
			}

			// Tests equality of two spherical points.
			function pointEqual(a, b) {
				return a && b && a[0] === b[0] && a[1] === b[1];
			}

			// Converts an array of n face vertices to an array of n + 1 edges.
			function faceEdges(face) {
				var n = face.length,
					edges = [];
				for(var a = face[n - 1], i = 0; i < n; ++i) edges.push([a, a = face[i]]);
				return edges;
			}

			function hasInverse(node) {
				return node.project.invert || node.children && node.children.some(hasInverse);
			}

		};

	}, {}],
	27: [function(require, module, exports) {
		module.exports = function(d3) {
			d3.geo.project = function(object, projection) {
				var stream = projection.stream;
				if(!stream) throw new Error("not yet supported");
				return(object && d3_geo_projectObjectType.hasOwnProperty(object.type) ? d3_geo_projectObjectType[object.type] : d3_geo_projectGeometry)(object, stream);
			};

			function d3_geo_projectFeature(object, stream) {
				return {
					type: "Feature",
					id: object.id,
					properties: object.properties,
					geometry: d3_geo_projectGeometry(object.geometry, stream)
				};
			}

			function d3_geo_projectGeometry(geometry, stream) {
				if(!geometry) return null;
				if(geometry.type === "GeometryCollection") return {
					type: "GeometryCollection",
					geometries: object.geometries.map(function(geometry) {
						return d3_geo_projectGeometry(geometry, stream);
					})
				};
				if(!d3_geo_projectGeometryType.hasOwnProperty(geometry.type)) return null;
				var sink = d3_geo_projectGeometryType[geometry.type];
				d3.geo.stream(geometry, stream(sink));
				return sink.result();
			}
			var d3_geo_projectObjectType = {
				Feature: d3_geo_projectFeature,
				FeatureCollection: function(object, stream) {
					return {
						type: "FeatureCollection",
						features: object.features.map(function(feature) {
							return d3_geo_projectFeature(feature, stream);
						})
					};
				}
			};
			var d3_geo_projectPoints = [],
				d3_geo_projectLines = [];
			var d3_geo_projectPoint = {
				point: function(x, y) {
					d3_geo_projectPoints.push([x, y]);
				},
				result: function() {
					var result = !d3_geo_projectPoints.length ? null : d3_geo_projectPoints.length < 2 ? {
						type: "Point",
						coordinates: d3_geo_projectPoints[0]
					} : {
						type: "MultiPoint",
						coordinates: d3_geo_projectPoints
					};
					d3_geo_projectPoints = [];
					return result;
				}
			};
			var d3_geo_projectLine = {
				lineStart: d3_geo_projectNoop,
				point: function(x, y) {
					d3_geo_projectPoints.push([x, y]);
				},
				lineEnd: function() {
					if(d3_geo_projectPoints.length) d3_geo_projectLines.push(d3_geo_projectPoints),
						d3_geo_projectPoints = [];
				},
				result: function() {
					var result = !d3_geo_projectLines.length ? null : d3_geo_projectLines.length < 2 ? {
						type: "LineString",
						coordinates: d3_geo_projectLines[0]
					} : {
						type: "MultiLineString",
						coordinates: d3_geo_projectLines
					};
					d3_geo_projectLines = [];
					return result;
				}
			};
			var d3_geo_projectPolygon = {
				polygonStart: d3_geo_projectNoop,
				lineStart: d3_geo_projectNoop,
				point: function(x, y) {
					d3_geo_projectPoints.push([x, y]);
				},
				lineEnd: function() {
					var n = d3_geo_projectPoints.length;
					if(n) {
						do d3_geo_projectPoints.push(d3_geo_projectPoints[0].slice()); while (++n < 4);
						d3_geo_projectLines.push(d3_geo_projectPoints), d3_geo_projectPoints = [];
					}
				},
				polygonEnd: d3_geo_projectNoop,
				result: function() {
					if(!d3_geo_projectLines.length) return null;
					var polygons = [],
						holes = [];
					d3_geo_projectLines.forEach(function(ring) {
						if(d3_geo_projectClockwise(ring)) polygons.push([ring]);
						else holes.push(ring);
					});
					holes.forEach(function(hole) {
						var point = hole[0];
						polygons.some(function(polygon) {
							if(d3_geo_projectContains(polygon[0], point)) {
								polygon.push(hole);
								return true;
							}
						}) || polygons.push([hole]);
					});
					d3_geo_projectLines = [];
					return !polygons.length ? null : polygons.length > 1 ? {
						type: "MultiPolygon",
						coordinates: polygons
					} : {
						type: "Polygon",
						coordinates: polygons[0]
					};
				}
			};
			var d3_geo_projectGeometryType = {
				Point: d3_geo_projectPoint,
				MultiPoint: d3_geo_projectPoint,
				LineString: d3_geo_projectLine,
				MultiLineString: d3_geo_projectLine,
				Polygon: d3_geo_projectPolygon,
				MultiPolygon: d3_geo_projectPolygon,
				Sphere: d3_geo_projectPolygon
			};

			function d3_geo_projectNoop() {}

			function d3_geo_projectClockwise(ring) {
				if((n = ring.length) < 4) return false;
				var i = 0,
					n, area = ring[n - 1][1] * ring[0][0] - ring[n - 1][0] * ring[0][1];
				while(++i < n) area += ring[i - 1][1] * ring[i][0] - ring[i - 1][0] * ring[i][1];
				return area <= 0;
			}

			function d3_geo_projectContains(ring, point) {
				var x = point[0],
					y = point[1],
					contains = false;
				for(var i = 0, n = ring.length, j = n - 1; i < n; j = i++) {
					var pi = ring[i],
						xi = pi[0],
						yi = pi[1],
						pj = ring[j],
						xj = pj[0],
						yj = pj[1];
					if(yi > y ^ yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) contains = !contains;
				}
				return contains;
			}
			var ε = 1e-6,
				ε2 = ε * ε,
				π = Math.PI,
				halfπ = π / 2,
				sqrtπ = Math.sqrt(π),
				radians = π / 180,
				degrees = 180 / π;

			function sinci(x) {
				return x ? x / Math.sin(x) : 1;
			}

			function sgn(x) {
				return x > 0 ? 1 : x < 0 ? -1 : 0;
			}

			function asin(x) {
				return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x);
			}

			function acos(x) {
				return x > 1 ? 0 : x < -1 ? π : Math.acos(x);
			}

			function asqrt(x) {
				return x > 0 ? Math.sqrt(x) : 0;
			}
			var projection = d3.geo.projection,
				projectionMutator = d3.geo.projectionMutator;
			d3.geo.interrupt = function(project) {
				var lobes = [
					[
						[
							[-π, 0],
							[0, halfπ],
							[π, 0]
						]
					],
					[
						[
							[-π, 0],
							[0, -halfπ],
							[π, 0]
						]
					]
				];
				var bounds;

				function forward(λ, φ) {
					var sign = φ < 0 ? -1 : +1,
						hemilobes = lobes[+(φ < 0)];
					for(var i = 0, n = hemilobes.length - 1; i < n && λ > hemilobes[i][2][0]; ++i);
					var coordinates = project(λ - hemilobes[i][1][0], φ);
					coordinates[0] += project(hemilobes[i][1][0], sign * φ > sign * hemilobes[i][0][1] ? hemilobes[i][0][1] : φ)[0];
					return coordinates;
				}

				function reset() {
					bounds = lobes.map(function(hemilobes) {
						return hemilobes.map(function(lobe) {
							var x0 = project(lobe[0][0], lobe[0][1])[0],
								x1 = project(lobe[2][0], lobe[2][1])[0],
								y0 = project(lobe[1][0], lobe[0][1])[1],
								y1 = project(lobe[1][0], lobe[1][1])[1],
								t;
							if(y0 > y1) t = y0, y0 = y1, y1 = t;
							return [
								[x0, y0],
								[x1, y1]
							];
						});
					});
				}
				if(project.invert) forward.invert = function(x, y) {
					var hemibounds = bounds[+(y < 0)],
						hemilobes = lobes[+(y < 0)];
					for(var i = 0, n = hemibounds.length; i < n; ++i) {
						var b = hemibounds[i];
						if(b[0][0] <= x && x < b[1][0] && b[0][1] <= y && y < b[1][1]) {
							var coordinates = project.invert(x - project(hemilobes[i][1][0], 0)[0], y);
							coordinates[0] += hemilobes[i][1][0];
							return pointEqual(forward(coordinates[0], coordinates[1]), [x, y]) ? coordinates : null;
						}
					}
				};
				var projection = d3.geo.projection(forward),
					stream_ = projection.stream;
				projection.stream = function(stream) {
					var rotate = projection.rotate(),
						rotateStream = stream_(stream),
						sphereStream = (projection.rotate([0, 0]),
							stream_(stream));
					projection.rotate(rotate);
					rotateStream.sphere = function() {
						d3.geo.stream(sphere(), sphereStream);
					};
					return rotateStream;
				};
				projection.lobes = function(_) {
					if(!arguments.length) return lobes.map(function(lobes) {
						return lobes.map(function(lobe) {
							return [
								[lobe[0][0] * 180 / π, lobe[0][1] * 180 / π],
								[lobe[1][0] * 180 / π, lobe[1][1] * 180 / π],
								[lobe[2][0] * 180 / π, lobe[2][1] * 180 / π]
							];
						});
					});
					lobes = _.map(function(lobes) {
						return lobes.map(function(lobe) {
							return [
								[lobe[0][0] * π / 180, lobe[0][1] * π / 180],
								[lobe[1][0] * π / 180, lobe[1][1] * π / 180],
								[lobe[2][0] * π / 180, lobe[2][1] * π / 180]
							];
						});
					});
					reset();
					return projection;
				};

				function sphere() {
					var ε = 1e-6,
						coordinates = [];
					for(var i = 0, n = lobes[0].length; i < n; ++i) {
						var lobe = lobes[0][i],
							λ0 = lobe[0][0] * 180 / π,
							φ0 = lobe[0][1] * 180 / π,
							φ1 = lobe[1][1] * 180 / π,
							λ2 = lobe[2][0] * 180 / π,
							φ2 = lobe[2][1] * 180 / π;
						coordinates.push(resample([
							[λ0 + ε, φ0 + ε],
							[λ0 + ε, φ1 - ε],
							[λ2 - ε, φ1 - ε],
							[λ2 - ε, φ2 + ε]
						], 30));
					}
					for(var i = lobes[1].length - 1; i >= 0; --i) {
						var lobe = lobes[1][i],
							λ0 = lobe[0][0] * 180 / π,
							φ0 = lobe[0][1] * 180 / π,
							φ1 = lobe[1][1] * 180 / π,
							λ2 = lobe[2][0] * 180 / π,
							φ2 = lobe[2][1] * 180 / π;
						coordinates.push(resample([
							[λ2 - ε, φ2 - ε],
							[λ2 - ε, φ1 + ε],
							[λ0 + ε, φ1 + ε],
							[λ0 + ε, φ0 - ε]
						], 30));
					}
					return {
						type: "Polygon",
						coordinates: [d3.merge(coordinates)]
					};
				}

				function resample(coordinates, m) {
					var i = -1,
						n = coordinates.length,
						p0 = coordinates[0],
						p1, dx, dy, resampled = [];
					while(++i < n) {
						p1 = coordinates[i];
						dx = (p1[0] - p0[0]) / m;
						dy = (p1[1] - p0[1]) / m;
						for(var j = 0; j < m; ++j) resampled.push([p0[0] + j * dx, p0[1] + j * dy]);
						p0 = p1;
					}
					resampled.push(p1);
					return resampled;
				}

				function pointEqual(a, b) {
					return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε;
				}
				return projection;
			};

			function airy(β) {
				var tanβ_2 = Math.tan(.5 * β),
					B = 2 * Math.log(Math.cos(.5 * β)) / (tanβ_2 * tanβ_2);

				function forward(λ, φ) {
					var cosλ = Math.cos(λ),
						cosφ = Math.cos(φ),
						sinφ = Math.sin(φ),
						cosz = cosφ * cosλ,
						K = -((1 - cosz ? Math.log(.5 * (1 + cosz)) / (1 - cosz) : -.5) + B / (1 + cosz));
					return [K * cosφ * Math.sin(λ), K * sinφ];
				}
				forward.invert = function(x, y) {
					var ρ = Math.sqrt(x * x + y * y),
						z = β * -.5,
						i = 50,
						δ;
					if(!ρ) return [0, 0];
					do {
						var z_2 = .5 * z,
							cosz_2 = Math.cos(z_2),
							sinz_2 = Math.sin(z_2),
							tanz_2 = Math.tan(z_2),
							lnsecz_2 = Math.log(1 / cosz_2);
						z -= δ = (2 / tanz_2 * lnsecz_2 - B * tanz_2 - ρ) / (-lnsecz_2 / (sinz_2 * sinz_2) + 1 - B / (2 * cosz_2 * cosz_2));
					} while (Math.abs(δ) > ε && --i > 0);
					var sinz = Math.sin(z);
					return [Math.atan2(x * sinz, ρ * Math.cos(z)), asin(y * sinz / ρ)];
				};
				return forward;
			}

			function airyProjection() {
				var β = halfπ,
					m = projectionMutator(airy),
					p = m(β);
				p.radius = function(_) {
					if(!arguments.length) return β / π * 180;
					return m(β = _ * π / 180);
				};
				return p;
			}
			(d3.geo.airy = airyProjection).raw = airy;

			function aitoff(λ, φ) {
				var cosφ = Math.cos(φ),
					sinciα = sinci(acos(cosφ * Math.cos(λ /= 2)));
				return [2 * cosφ * Math.sin(λ) * sinciα, Math.sin(φ) * sinciα];
			}
			aitoff.invert = function(x, y) {
				if(x * x + 4 * y * y > π * π + ε) return;
				var λ = x,
					φ = y,
					i = 25;
				do {
					var sinλ = Math.sin(λ),
						sinλ_2 = Math.sin(λ / 2),
						cosλ_2 = Math.cos(λ / 2),
						sinφ = Math.sin(φ),
						cosφ = Math.cos(φ),
						sin_2φ = Math.sin(2 * φ),
						sin2φ = sinφ * sinφ,
						cos2φ = cosφ * cosφ,
						sin2λ_2 = sinλ_2 * sinλ_2,
						C = 1 - cos2φ * cosλ_2 * cosλ_2,
						E = C ? acos(cosφ * cosλ_2) * Math.sqrt(F = 1 / C) : F = 0,
						F, fx = 2 * E * cosφ * sinλ_2 - x,
						fy = E * sinφ - y,
						δxδλ = F * (cos2φ * sin2λ_2 + E * cosφ * cosλ_2 * sin2φ),
						δxδφ = F * (.5 * sinλ * sin_2φ - E * 2 * sinφ * sinλ_2),
						δyδλ = F * .25 * (sin_2φ * sinλ_2 - E * sinφ * cos2φ * sinλ),
						δyδφ = F * (sin2φ * cosλ_2 + E * sin2λ_2 * cosφ),
						denominator = δxδφ * δyδλ - δyδφ * δxδλ;
					if(!denominator) break;
					var δλ = (fy * δxδφ - fx * δyδφ) / denominator,
						δφ = (fx * δyδλ - fy * δxδλ) / denominator;
					λ -= δλ, φ -= δφ;
				} while ((Math.abs(δλ) > ε || Math.abs(δφ) > ε) && --i > 0);
				return [λ, φ];
			};
			(d3.geo.aitoff = function() {
				return projection(aitoff);
			}).raw = aitoff;

			function armadillo(φ0) {
				var sinφ0 = Math.sin(φ0),
					cosφ0 = Math.cos(φ0),
					sφ0 = φ0 > 0 ? 1 : -1,
					tanφ0 = Math.tan(sφ0 * φ0),
					k = (1 + sinφ0 - cosφ0) / 2;

				function forward(λ, φ) {
					var cosφ = Math.cos(φ),
						cosλ = Math.cos(λ /= 2);
					return [(1 + cosφ) * Math.sin(λ), (sφ0 * φ > -Math.atan2(cosλ, tanφ0) - .001 ? 0 : -sφ0 * 10) + k + Math.sin(φ) * cosφ0 - (1 + cosφ) * sinφ0 * cosλ];
				}
				forward.invert = function(x, y) {
					var λ = 0,
						φ = 0,
						i = 50;
					do {
						var cosλ = Math.cos(λ),
							sinλ = Math.sin(λ),
							cosφ = Math.cos(φ),
							sinφ = Math.sin(φ),
							A = 1 + cosφ,
							fx = A * sinλ - x,
							fy = k + sinφ * cosφ0 - A * sinφ0 * cosλ - y,
							δxδλ = .5 * A * cosλ,
							δxδφ = -sinλ * sinφ,
							δyδλ = .5 * sinφ0 * A * sinλ,
							δyδφ = cosφ0 * cosφ + sinφ0 * cosλ * sinφ,
							denominator = δxδφ * δyδλ - δyδφ * δxδλ,
							δλ = .5 * (fy * δxδφ - fx * δyδφ) / denominator,
							δφ = (fx * δyδλ - fy * δxδλ) / denominator;
						λ -= δλ, φ -= δφ;
					} while ((Math.abs(δλ) > ε || Math.abs(δφ) > ε) && --i > 0);
					return sφ0 * φ > -Math.atan2(Math.cos(λ), tanφ0) - .001 ? [λ * 2, φ] : null;
				};
				return forward;
			}

			function armadilloProjection() {
				var φ0 = π / 9,
					sφ0 = φ0 > 0 ? 1 : -1,
					tanφ0 = Math.tan(sφ0 * φ0),
					m = projectionMutator(armadillo),
					p = m(φ0),
					stream_ = p.stream;
				p.parallel = function(_) {
					if(!arguments.length) return φ0 / π * 180;
					tanφ0 = Math.tan((sφ0 = (φ0 = _ * π / 180) > 0 ? 1 : -1) * φ0);
					return m(φ0);
				};
				p.stream = function(stream) {
					var rotate = p.rotate(),
						rotateStream = stream_(stream),
						sphereStream = (p.rotate([0, 0]),
							stream_(stream));
					p.rotate(rotate);
					rotateStream.sphere = function() {
						sphereStream.polygonStart(), sphereStream.lineStart();
						for(var λ = sφ0 * -180; sφ0 * λ < 180; λ += sφ0 * 90) sphereStream.point(λ, sφ0 * 90);
						while(sφ0 * (λ -= φ0) >= -180) {
							sphereStream.point(λ, sφ0 * -Math.atan2(Math.cos(λ * radians / 2), tanφ0) * degrees);
						}
						sphereStream.lineEnd(), sphereStream.polygonEnd();
					};
					return rotateStream;
				};
				return p;
			}
			(d3.geo.armadillo = armadilloProjection).raw = armadillo;

			function tanh(x) {
				x = Math.exp(2 * x);
				return(x - 1) / (x + 1);
			}

			function sinh(x) {
				return .5 * (Math.exp(x) - Math.exp(-x));
			}

			function cosh(x) {
				return .5 * (Math.exp(x) + Math.exp(-x));
			}

			function arsinh(x) {
				return Math.log(x + asqrt(x * x + 1));
			}

			function arcosh(x) {
				return Math.log(x + asqrt(x * x - 1));
			}

			function august(λ, φ) {
				var tanφ = Math.tan(φ / 2),
					k = asqrt(1 - tanφ * tanφ),
					c = 1 + k * Math.cos(λ /= 2),
					x = Math.sin(λ) * k / c,
					y = tanφ / c,
					x2 = x * x,
					y2 = y * y;
				return [4 / 3 * x * (3 + x2 - 3 * y2), 4 / 3 * y * (3 + 3 * x2 - y2)];
			}
			august.invert = function(x, y) {
				x *= 3 / 8, y *= 3 / 8;
				if(!x && Math.abs(y) > 1) return null;
				var x2 = x * x,
					y2 = y * y,
					s = 1 + x2 + y2,
					sin3η = Math.sqrt(.5 * (s - Math.sqrt(s * s - 4 * y * y))),
					η = asin(sin3η) / 3,
					ξ = sin3η ? arcosh(Math.abs(y / sin3η)) / 3 : arsinh(Math.abs(x)) / 3,
					cosη = Math.cos(η),
					coshξ = cosh(ξ),
					d = coshξ * coshξ - cosη * cosη;
				return [sgn(x) * 2 * Math.atan2(sinh(ξ) * cosη, .25 - d), sgn(y) * 2 * Math.atan2(coshξ * Math.sin(η), .25 + d)];
			};
			(d3.geo.august = function() {
				return projection(august);
			}).raw = august;
			var bakerφ = Math.log(1 + Math.SQRT2);

			function baker(λ, φ) {
				var φ0 = Math.abs(φ);
				return φ0 < π / 4 ? [λ, Math.log(Math.tan(π / 4 + φ / 2))] : [λ * Math.cos(φ0) * (2 * Math.SQRT2 - 1 / Math.sin(φ0)), sgn(φ) * (2 * Math.SQRT2 * (φ0 - π / 4) - Math.log(Math.tan(φ0 / 2)))];
			}
			baker.invert = function(x, y) {
				if((y0 = Math.abs(y)) < bakerφ) return [x, 2 * Math.atan(Math.exp(y)) - halfπ];
				var sqrt8 = Math.sqrt(8),
					φ = π / 4,
					i = 25,
					δ, y0;
				do {
					var cosφ_2 = Math.cos(φ / 2),
						tanφ_2 = Math.tan(φ / 2);
					φ -= δ = (sqrt8 * (φ - π / 4) - Math.log(tanφ_2) - y0) / (sqrt8 - .5 * cosφ_2 * cosφ_2 / tanφ_2);
				} while (Math.abs(δ) > ε2 && --i > 0);
				return [x / (Math.cos(φ) * (sqrt8 - 1 / Math.sin(φ))), sgn(y) * φ];
			};
			(d3.geo.baker = function() {
				return projection(baker);
			}).raw = baker;
			var berghausAzimuthalEquidistant = d3.geo.azimuthalEquidistant.raw;

			function berghaus(n) {
				var k = 2 * π / n;

				function forward(λ, φ) {
					var p = berghausAzimuthalEquidistant(λ, φ);
					if(Math.abs(λ) > halfπ) {
						var θ = Math.atan2(p[1], p[0]),
							r = Math.sqrt(p[0] * p[0] + p[1] * p[1]),
							θ0 = k * Math.round((θ - halfπ) / k) + halfπ,
							α = Math.atan2(Math.sin(θ -= θ0), 2 - Math.cos(θ));
						θ = θ0 + asin(π / r * Math.sin(α)) - α;
						p[0] = r * Math.cos(θ);
						p[1] = r * Math.sin(θ);
					}
					return p;
				}
				forward.invert = function(x, y) {
					var r = Math.sqrt(x * x + y * y);
					if(r > halfπ) {
						var θ = Math.atan2(y, x),
							θ0 = k * Math.round((θ - halfπ) / k) + halfπ,
							s = θ > θ0 ? -1 : 1,
							A = r * Math.cos(θ0 - θ),
							cotα = 1 / Math.tan(s * Math.acos((A - π) / Math.sqrt(π * (π - 2 * A) + r * r)));
						θ = θ0 + 2 * Math.atan((cotα + s * Math.sqrt(cotα * cotα - 3)) / 3);
						x = r * Math.cos(θ), y = r * Math.sin(θ);
					}
					return berghausAzimuthalEquidistant.invert(x, y);
				};
				return forward;
			}

			function berghausProjection() {
				var n = 5,
					m = projectionMutator(berghaus),
					p = m(n),
					stream_ = p.stream,
					ε = .01,
					cr = -Math.cos(ε * radians),
					sr = Math.sin(ε * radians);
				p.lobes = function(_) {
					if(!arguments.length) return n;
					return m(n = +_);
				};
				p.stream = function(stream) {
					var rotate = p.rotate(),
						rotateStream = stream_(stream),
						sphereStream = (p.rotate([0, 0]),
							stream_(stream));
					p.rotate(rotate);
					rotateStream.sphere = function() {
						sphereStream.polygonStart(), sphereStream.lineStart();
						for(var i = 0, δ = 360 / n, δ0 = 2 * π / n, φ = 90 - 180 / n, φ0 = halfπ; i < n; ++i,
							φ -= δ, φ0 -= δ0) {
							sphereStream.point(Math.atan2(sr * Math.cos(φ0), cr) * degrees, asin(sr * Math.sin(φ0)) * degrees);
							if(φ < -90) {
								sphereStream.point(-90, -180 - φ - ε);
								sphereStream.point(-90, -180 - φ + ε);
							} else {
								sphereStream.point(90, φ + ε);
								sphereStream.point(90, φ - ε);
							}
						}
						sphereStream.lineEnd(), sphereStream.polygonEnd();
					};
					return rotateStream;
				};
				return p;
			}
			(d3.geo.berghaus = berghausProjection).raw = berghaus;

			function mollweideBromleyθ(Cp) {
				return function(θ) {
					var Cpsinθ = Cp * Math.sin(θ),
						i = 30,
						δ;
					do θ -= δ = (θ + Math.sin(θ) - Cpsinθ) / (1 + Math.cos(θ)); while (Math.abs(δ) > ε && --i > 0);
					return θ / 2;
				};
			}

			function mollweideBromley(Cx, Cy, Cp) {
				var θ = mollweideBromleyθ(Cp);

				function forward(λ, φ) {
					return [Cx * λ * Math.cos(φ = θ(φ)), Cy * Math.sin(φ)];
				}
				forward.invert = function(x, y) {
					var θ = asin(y / Cy);
					return [x / (Cx * Math.cos(θ)), asin((2 * θ + Math.sin(2 * θ)) / Cp)];
				};
				return forward;
			}
			var mollweideθ = mollweideBromleyθ(π),
				mollweide = mollweideBromley(Math.SQRT2 / halfπ, Math.SQRT2, π);
			(d3.geo.mollweide = function() {
				return projection(mollweide);
			}).raw = mollweide;

			function boggs(λ, φ) {
				var k = 2.00276,
					θ = mollweideθ(φ);
				return [k * λ / (1 / Math.cos(φ) + 1.11072 / Math.cos(θ)), (φ + Math.SQRT2 * Math.sin(θ)) / k];
			}
			boggs.invert = function(x, y) {
				var k = 2.00276,
					ky = k * y,
					θ = y < 0 ? -π / 4 : π / 4,
					i = 25,
					δ, φ;
				do {
					φ = ky - Math.SQRT2 * Math.sin(θ);
					θ -= δ = (Math.sin(2 * θ) + 2 * θ - π * Math.sin(φ)) / (2 * Math.cos(2 * θ) + 2 + π * Math.cos(φ) * Math.SQRT2 * Math.cos(θ));
				} while (Math.abs(δ) > ε && --i > 0);
				φ = ky - Math.SQRT2 * Math.sin(θ);
				return [x * (1 / Math.cos(φ) + 1.11072 / Math.cos(θ)) / k, φ];
			};
			(d3.geo.boggs = function() {
				return projection(boggs);
			}).raw = boggs;

			function parallel1Projection(projectAt) {
				var φ0 = 0,
					m = projectionMutator(projectAt),
					p = m(φ0);
				p.parallel = function(_) {
					if(!arguments.length) return φ0 / π * 180;
					return m(φ0 = _ * π / 180);
				};
				return p;
			}

			function sinusoidal(λ, φ) {
				return [λ * Math.cos(φ), φ];
			}
			sinusoidal.invert = function(x, y) {
				return [x / Math.cos(y), y];
			};
			(d3.geo.sinusoidal = function() {
				return projection(sinusoidal);
			}).raw = sinusoidal;

			function bonne(φ0) {
				if(!φ0) return sinusoidal;
				var cotφ0 = 1 / Math.tan(φ0);

				function forward(λ, φ) {
					var ρ = cotφ0 + φ0 - φ,
						E = ρ ? λ * Math.cos(φ) / ρ : ρ;
					return [ρ * Math.sin(E), cotφ0 - ρ * Math.cos(E)];
				}
				forward.invert = function(x, y) {
					var ρ = Math.sqrt(x * x + (y = cotφ0 - y) * y),
						φ = cotφ0 + φ0 - ρ;
					return [ρ / Math.cos(φ) * Math.atan2(x, y), φ];
				};
				return forward;
			}
			(d3.geo.bonne = function() {
				return parallel1Projection(bonne).parallel(45);
			}).raw = bonne;
			var bromley = mollweideBromley(1, 4 / π, π);
			(d3.geo.bromley = function() {
				return projection(bromley);
			}).raw = bromley;

			function chamberlin(points) {
				points = points.map(function(p) {
					return [p[0], p[1], Math.sin(p[1]), Math.cos(p[1])];
				});
				for(var a = points[2], b, i = 0; i < 3; ++i, a = b) {
					b = points[i];
					a.v = chamberlinDistanceAzimuth(b[1] - a[1], a[3], a[2], b[3], b[2], b[0] - a[0]);
					a.point = [0, 0];
				}
				var β0 = chamberlinAngle(points[0].v[0], points[2].v[0], points[1].v[0]),
					β1 = chamberlinAngle(points[0].v[0], points[1].v[0], points[2].v[0]),
					β2 = π - β0;
				points[2].point[1] = 0;
				points[0].point[0] = -(points[1].point[0] = .5 * points[0].v[0]);
				var mean = [points[2].point[0] = points[0].point[0] + points[2].v[0] * Math.cos(β0), 2 * (points[0].point[1] = points[1].point[1] = points[2].v[0] * Math.sin(β0))];

				function forward(λ, φ) {
					var sinφ = Math.sin(φ),
						cosφ = Math.cos(φ),
						v = new Array(3);
					for(var i = 0; i < 3; ++i) {
						var p = points[i];
						v[i] = chamberlinDistanceAzimuth(φ - p[1], p[3], p[2], cosφ, sinφ, λ - p[0]);
						if(!v[i][0]) return p.point;
						v[i][1] = chamberlinLongitude(v[i][1] - p.v[1]);
					}
					var point = mean.slice();
					for(var i = 0; i < 3; ++i) {
						var j = i == 2 ? 0 : i + 1;
						var a = chamberlinAngle(points[i].v[0], v[i][0], v[j][0]);
						if(v[i][1] < 0) a = -a;
						if(!i) {
							point[0] += v[i][0] * Math.cos(a);
							point[1] -= v[i][0] * Math.sin(a);
						} else if(i == 1) {
							a = β1 - a;
							point[0] -= v[i][0] * Math.cos(a);
							point[1] -= v[i][0] * Math.sin(a);
						} else {
							a = β2 - a;
							point[0] += v[i][0] * Math.cos(a);
							point[1] += v[i][0] * Math.sin(a);
						}
					}
					point[0] /= 3, point[1] /= 3;
					return point;
				}
				return forward;
			}

			function chamberlinProjection() {
				var points = [
						[0, 0],
						[0, 0],
						[0, 0]
					],
					m = projectionMutator(chamberlin),
					p = m(points),
					rotate = p.rotate;
				delete p.rotate;
				p.points = function(_) {
					if(!arguments.length) return points;
					points = _;
					var origin = d3.geo.centroid({
							type: "MultiPoint",
							coordinates: points
						}),
						r = [-origin[0], -origin[1]];
					rotate.call(p, r);
					return m(points.map(d3.geo.rotation(r)).map(chamberlinRadians));
				};
				return p.points([
					[-150, 55],
					[-35, 55],
					[-92.5, 10]
				]);
			}

			function chamberlinDistanceAzimuth(dφ, c1, s1, c2, s2, dλ) {
				var cosdλ = Math.cos(dλ),
					r;
				if(Math.abs(dφ) > 1 || Math.abs(dλ) > 1) {
					r = acos(s1 * s2 + c1 * c2 * cosdλ);
				} else {
					var sindφ = Math.sin(.5 * dφ),
						sindλ = Math.sin(.5 * dλ);
					r = 2 * asin(Math.sqrt(sindφ * sindφ + c1 * c2 * sindλ * sindλ));
				}
				if(Math.abs(r) > ε) {
					return [r, Math.atan2(c2 * Math.sin(dλ), c1 * s2 - s1 * c2 * cosdλ)];
				}
				return [0, 0];
			}

			function chamberlinAngle(b, c, a) {
				return acos(.5 * (b * b + c * c - a * a) / (b * c));
			}

			function chamberlinLongitude(λ) {
				return λ - 2 * π * Math.floor((λ + π) / (2 * π));
			}

			function chamberlinRadians(point) {
				return [point[0] * radians, point[1] * radians];
			}
			(d3.geo.chamberlin = chamberlinProjection).raw = chamberlin;

			function collignon(λ, φ) {
				var α = asqrt(1 - Math.sin(φ));
				return [2 / sqrtπ * λ * α, sqrtπ * (1 - α)];
			}
			collignon.invert = function(x, y) {
				var λ = (λ = y / sqrtπ - 1) * λ;
				return [λ > 0 ? x * Math.sqrt(π / λ) / 2 : 0, asin(1 - λ)];
			};
			(d3.geo.collignon = function() {
				return projection(collignon);
			}).raw = collignon;

			function craig(φ0) {
				var tanφ0 = Math.tan(φ0);

				function forward(λ, φ) {
					return [λ, (λ ? λ / Math.sin(λ) : 1) * (Math.sin(φ) * Math.cos(λ) - tanφ0 * Math.cos(φ))];
				}
				forward.invert = tanφ0 ? function(x, y) {
					if(x) y *= Math.sin(x) / x;
					var cosλ = Math.cos(x);
					return [x, 2 * Math.atan2(Math.sqrt(cosλ * cosλ + tanφ0 * tanφ0 - y * y) - cosλ, tanφ0 - y)];
				} : function(x, y) {
					return [x, asin(x ? y * Math.tan(x) / x : y)];
				};
				return forward;
			}
			(d3.geo.craig = function() {
				return parallel1Projection(craig);
			}).raw = craig;

			function craster(λ, φ) {
				var sqrt3 = Math.sqrt(3);
				return [sqrt3 * λ * (2 * Math.cos(2 * φ / 3) - 1) / sqrtπ, sqrt3 * sqrtπ * Math.sin(φ / 3)];
			}
			craster.invert = function(x, y) {
				var sqrt3 = Math.sqrt(3),
					φ = 3 * asin(y / (sqrt3 * sqrtπ));
				return [sqrtπ * x / (sqrt3 * (2 * Math.cos(2 * φ / 3) - 1)), φ];
			};
			(d3.geo.craster = function() {
				return projection(craster);
			}).raw = craster;

			function cylindricalEqualArea(φ0) {
				var cosφ0 = Math.cos(φ0);

				function forward(λ, φ) {
					return [λ * cosφ0, Math.sin(φ) / cosφ0];
				}
				forward.invert = function(x, y) {
					return [x / cosφ0, asin(y * cosφ0)];
				};
				return forward;
			}
			(d3.geo.cylindricalEqualArea = function() {
				return parallel1Projection(cylindricalEqualArea);
			}).raw = cylindricalEqualArea;

			function cylindricalStereographic(φ0) {
				var cosφ0 = Math.cos(φ0);

				function forward(λ, φ) {
					return [λ * cosφ0, (1 + cosφ0) * Math.tan(φ * .5)];
				}
				forward.invert = function(x, y) {
					return [x / cosφ0, Math.atan(y / (1 + cosφ0)) * 2];
				};
				return forward;
			}
			(d3.geo.cylindricalStereographic = function() {
				return parallel1Projection(cylindricalStereographic);
			}).raw = cylindricalStereographic;

			function eckert1(λ, φ) {
				var α = Math.sqrt(8 / (3 * π));
				return [α * λ * (1 - Math.abs(φ) / π), α * φ];
			}
			eckert1.invert = function(x, y) {
				var α = Math.sqrt(8 / (3 * π)),
					φ = y / α;
				return [x / (α * (1 - Math.abs(φ) / π)), φ];
			};
			(d3.geo.eckert1 = function() {
				return projection(eckert1);
			}).raw = eckert1;

			function eckert2(λ, φ) {
				var α = Math.sqrt(4 - 3 * Math.sin(Math.abs(φ)));
				return [2 / Math.sqrt(6 * π) * λ * α, sgn(φ) * Math.sqrt(2 * π / 3) * (2 - α)];
			}
			eckert2.invert = function(x, y) {
				var α = 2 - Math.abs(y) / Math.sqrt(2 * π / 3);
				return [x * Math.sqrt(6 * π) / (2 * α), sgn(y) * asin((4 - α * α) / 3)];
			};
			(d3.geo.eckert2 = function() {
				return projection(eckert2);
			}).raw = eckert2;

			function eckert3(λ, φ) {
				var k = Math.sqrt(π * (4 + π));
				return [2 / k * λ * (1 + Math.sqrt(1 - 4 * φ * φ / (π * π))), 4 / k * φ];
			}
			eckert3.invert = function(x, y) {
				var k = Math.sqrt(π * (4 + π)) / 2;
				return [x * k / (1 + asqrt(1 - y * y * (4 + π) / (4 * π))), y * k / 2];
			};
			(d3.geo.eckert3 = function() {
				return projection(eckert3);
			}).raw = eckert3;

			function eckert4(λ, φ) {
				var k = (2 + halfπ) * Math.sin(φ);
				φ /= 2;
				for(var i = 0, δ = Infinity; i < 10 && Math.abs(δ) > ε; i++) {
					var cosφ = Math.cos(φ);
					φ -= δ = (φ + Math.sin(φ) * (cosφ + 2) - k) / (2 * cosφ * (1 + cosφ));
				}
				return [2 / Math.sqrt(π * (4 + π)) * λ * (1 + Math.cos(φ)), 2 * Math.sqrt(π / (4 + π)) * Math.sin(φ)];
			}
			eckert4.invert = function(x, y) {
				var A = .5 * y * Math.sqrt((4 + π) / π),
					k = asin(A),
					c = Math.cos(k);
				return [x / (2 / Math.sqrt(π * (4 + π)) * (1 + c)), asin((k + A * (c + 2)) / (2 + halfπ))];
			};
			(d3.geo.eckert4 = function() {
				return projection(eckert4);
			}).raw = eckert4;

			function eckert5(λ, φ) {
				return [λ * (1 + Math.cos(φ)) / Math.sqrt(2 + π), 2 * φ / Math.sqrt(2 + π)];
			}
			eckert5.invert = function(x, y) {
				var k = Math.sqrt(2 + π),
					φ = y * k / 2;
				return [k * x / (1 + Math.cos(φ)), φ];
			};
			(d3.geo.eckert5 = function() {
				return projection(eckert5);
			}).raw = eckert5;

			function eckert6(λ, φ) {
				var k = (1 + halfπ) * Math.sin(φ);
				for(var i = 0, δ = Infinity; i < 10 && Math.abs(δ) > ε; i++) {
					φ -= δ = (φ + Math.sin(φ) - k) / (1 + Math.cos(φ));
				}
				k = Math.sqrt(2 + π);
				return [λ * (1 + Math.cos(φ)) / k, 2 * φ / k];
			}
			eckert6.invert = function(x, y) {
				var j = 1 + halfπ,
					k = Math.sqrt(j / 2);
				return [x * 2 * k / (1 + Math.cos(y *= k)), asin((y + Math.sin(y)) / j)];
			};
			(d3.geo.eckert6 = function() {
				return projection(eckert6);
			}).raw = eckert6;

			function eisenlohr(λ, φ) {
				var s0 = Math.sin(λ /= 2),
					c0 = Math.cos(λ),
					k = Math.sqrt(Math.cos(φ)),
					c1 = Math.cos(φ /= 2),
					t = Math.sin(φ) / (c1 + Math.SQRT2 * c0 * k),
					c = Math.sqrt(2 / (1 + t * t)),
					v = Math.sqrt((Math.SQRT2 * c1 + (c0 + s0) * k) / (Math.SQRT2 * c1 + (c0 - s0) * k));
				return [eisenlohrK * (c * (v - 1 / v) - 2 * Math.log(v)), eisenlohrK * (c * t * (v + 1 / v) - 2 * Math.atan(t))];
			}
			eisenlohr.invert = function(x, y) {
				var p = d3.geo.august.raw.invert(x / 1.2, y * 1.065);
				if(!p) return null;
				var λ = p[0],
					φ = p[1],
					i = 20;
				x /= eisenlohrK, y /= eisenlohrK;
				do {
					var _0 = λ / 2,
						_1 = φ / 2,
						s0 = Math.sin(_0),
						c0 = Math.cos(_0),
						s1 = Math.sin(_1),
						c1 = Math.cos(_1),
						cos1 = Math.cos(φ),
						k = Math.sqrt(cos1),
						t = s1 / (c1 + Math.SQRT2 * c0 * k),
						t2 = t * t,
						c = Math.sqrt(2 / (1 + t2)),
						v0 = Math.SQRT2 * c1 + (c0 + s0) * k,
						v1 = Math.SQRT2 * c1 + (c0 - s0) * k,
						v2 = v0 / v1,
						v = Math.sqrt(v2),
						vm1v = v - 1 / v,
						vp1v = v + 1 / v,
						fx = c * vm1v - 2 * Math.log(v) - x,
						fy = c * t * vp1v - 2 * Math.atan(t) - y,
						δtδλ = s1 && Math.SQRT1_2 * k * s0 * t2 / s1,
						δtδφ = (Math.SQRT2 * c0 * c1 + k) / (2 * (c1 + Math.SQRT2 * c0 * k) * (c1 + Math.SQRT2 * c0 * k) * k),
						δcδt = -.5 * t * c * c * c,
						δcδλ = δcδt * δtδλ,
						δcδφ = δcδt * δtδφ,
						A = (A = 2 * c1 + Math.SQRT2 * k * (c0 - s0)) * A * v,
						δvδλ = (Math.SQRT2 * c0 * c1 * k + cos1) / A,
						δvδφ = -(Math.SQRT2 * s0 * s1) / (k * A),
						δxδλ = vm1v * δcδλ - 2 * δvδλ / v + c * (δvδλ + δvδλ / v2),
						δxδφ = vm1v * δcδφ - 2 * δvδφ / v + c * (δvδφ + δvδφ / v2),
						δyδλ = t * vp1v * δcδλ - 2 * δtδλ / (1 + t2) + c * vp1v * δtδλ + c * t * (δvδλ - δvδλ / v2),
						δyδφ = t * vp1v * δcδφ - 2 * δtδφ / (1 + t2) + c * vp1v * δtδφ + c * t * (δvδφ - δvδφ / v2),
						denominator = δxδφ * δyδλ - δyδφ * δxδλ;
					if(!denominator) break;
					var δλ = (fy * δxδφ - fx * δyδφ) / denominator,
						δφ = (fx * δyδλ - fy * δxδλ) / denominator;
					λ -= δλ;
					φ = Math.max(-halfπ, Math.min(halfπ, φ - δφ));
				} while ((Math.abs(δλ) > ε || Math.abs(δφ) > ε) && --i > 0);
				return Math.abs(Math.abs(φ) - halfπ) < ε ? [0, φ] : i && [λ, φ];
			};
			var eisenlohrK = 3 + 2 * Math.SQRT2;
			(d3.geo.eisenlohr = function() {
				return projection(eisenlohr);
			}).raw = eisenlohr;

			function fahey(λ, φ) {
				var t = Math.tan(φ / 2);
				return [λ * faheyK * asqrt(1 - t * t), (1 + faheyK) * t];
			}
			fahey.invert = function(x, y) {
				var t = y / (1 + faheyK);
				return [x ? x / (faheyK * asqrt(1 - t * t)) : 0, 2 * Math.atan(t)];
			};
			var faheyK = Math.cos(35 * radians);
			(d3.geo.fahey = function() {
				return projection(fahey);
			}).raw = fahey;

			function foucaut(λ, φ) {
				var k = φ / 2,
					cosk = Math.cos(k);
				return [2 * λ / sqrtπ * Math.cos(φ) * cosk * cosk, sqrtπ * Math.tan(k)];
			}
			foucaut.invert = function(x, y) {
				var k = Math.atan(y / sqrtπ),
					cosk = Math.cos(k),
					φ = 2 * k;
				return [x * sqrtπ * .5 / (Math.cos(φ) * cosk * cosk), φ];
			};
			(d3.geo.foucaut = function() {
				return projection(foucaut);
			}).raw = foucaut;
			d3.geo.gilbert = function(projection) {
				var e = d3.geo.equirectangular().scale(degrees).translate([0, 0]);

				function gilbert(coordinates) {
					return projection([coordinates[0] * .5, asin(Math.tan(coordinates[1] * .5 * radians)) * degrees]);
				}
				if(projection.invert) gilbert.invert = function(coordinates) {
					coordinates = projection.invert(coordinates);
					coordinates[0] *= 2;
					coordinates[1] = 2 * Math.atan(Math.sin(coordinates[1] * radians)) * degrees;
					return coordinates;
				};
				gilbert.stream = function(stream) {
					stream = projection.stream(stream);
					var s = e.stream({
						point: function(λ, φ) {
							stream.point(λ * .5, asin(Math.tan(-φ * .5 * radians)) * degrees);
						},
						lineStart: function() {
							stream.lineStart();
						},
						lineEnd: function() {
							stream.lineEnd();
						},
						polygonStart: function() {
							stream.polygonStart();
						},
						polygonEnd: function() {
							stream.polygonEnd();
						}
					});
					s.sphere = function() {
						stream.sphere();
					};
					s.valid = false;
					return s;
				};
				return gilbert;
			};
			var gingeryAzimuthalEquidistant = d3.geo.azimuthalEquidistant.raw;

			function gingery(ρ, n) {
				var k = 2 * π / n,
					ρ2 = ρ * ρ;

				function forward(λ, φ) {
					var p = gingeryAzimuthalEquidistant(λ, φ),
						x = p[0],
						y = p[1],
						r2 = x * x + y * y;
					if(r2 > ρ2) {
						var r = Math.sqrt(r2),
							θ = Math.atan2(y, x),
							θ0 = k * Math.round(θ / k),
							α = θ - θ0,
							ρcosα = ρ * Math.cos(α),
							k_ = (ρ * Math.sin(α) - α * Math.sin(ρcosα)) / (halfπ - ρcosα),
							s_ = arcLength_(α, k_),
							e = (π - ρ) / gingeryIntegrate(s_, ρcosα, π);
						x = r;
						var i = 50,
							δ;
						do {
							x -= δ = (ρ + gingeryIntegrate(s_, ρcosα, x) * e - r) / (s_(x) * e);
						} while (Math.abs(δ) > ε && --i > 0);
						y = α * Math.sin(x);
						if(x < halfπ) y -= k_ * (x - halfπ);
						var s = Math.sin(θ0),
							c = Math.cos(θ0);
						p[0] = x * c - y * s;
						p[1] = x * s + y * c;
					}
					return p;
				}
				forward.invert = function(x, y) {
					var r2 = x * x + y * y;
					if(r2 > ρ2) {
						var r = Math.sqrt(r2),
							θ = Math.atan2(y, x),
							θ0 = k * Math.round(θ / k),
							dθ = θ - θ0,
							x = r * Math.cos(dθ);
						y = r * Math.sin(dθ);
						var x_halfπ = x - halfπ,
							sinx = Math.sin(x),
							α = y / sinx,
							δ = x < halfπ ? Infinity : 0,
							i = 10;
						while(true) {
							var ρsinα = ρ * Math.sin(α),
								ρcosα = ρ * Math.cos(α),
								sinρcosα = Math.sin(ρcosα),
								halfπ_ρcosα = halfπ - ρcosα,
								k_ = (ρsinα - α * sinρcosα) / halfπ_ρcosα,
								s_ = arcLength_(α, k_);
							if(Math.abs(δ) < ε2 || !--i) break;
							α -= δ = (α * sinx - k_ * x_halfπ - y) / (sinx - x_halfπ * 2 * (halfπ_ρcosα * (ρcosα + α * ρsinα * Math.cos(ρcosα) - sinρcosα) - ρsinα * (ρsinα - α * sinρcosα)) / (halfπ_ρcosα * halfπ_ρcosα));
						}
						r = ρ + gingeryIntegrate(s_, ρcosα, x) * (π - ρ) / gingeryIntegrate(s_, ρcosα, π);
						θ = θ0 + α;
						x = r * Math.cos(θ);
						y = r * Math.sin(θ);
					}
					return gingeryAzimuthalEquidistant.invert(x, y);
				};
				return forward;
			}

			function arcLength_(α, k) {
				return function(x) {
					var y_ = α * Math.cos(x);
					if(x < halfπ) y_ -= k;
					return Math.sqrt(1 + y_ * y_);
				};
			}

			function gingeryProjection() {
				var n = 6,
					ρ = 30 * radians,
					cρ = Math.cos(ρ),
					sρ = Math.sin(ρ),
					m = projectionMutator(gingery),
					p = m(ρ, n),
					stream_ = p.stream,
					ε = .01,
					cr = -Math.cos(ε * radians),
					sr = Math.sin(ε * radians);
				p.radius = function(_) {
					if(!arguments.length) return ρ * degrees;
					cρ = Math.cos(ρ = _ * radians);
					sρ = Math.sin(ρ);
					return m(ρ, n);
				};
				p.lobes = function(_) {
					if(!arguments.length) return n;
					return m(ρ, n = +_);
				};
				p.stream = function(stream) {
					var rotate = p.rotate(),
						rotateStream = stream_(stream),
						sphereStream = (p.rotate([0, 0]),
							stream_(stream));
					p.rotate(rotate);
					rotateStream.sphere = function() {
						sphereStream.polygonStart(), sphereStream.lineStart();
						for(var i = 0, δ = 2 * π / n, φ = 0; i < n; ++i, φ -= δ) {
							sphereStream.point(Math.atan2(sr * Math.cos(φ), cr) * degrees, Math.asin(sr * Math.sin(φ)) * degrees);
							sphereStream.point(Math.atan2(sρ * Math.cos(φ - δ / 2), cρ) * degrees, Math.asin(sρ * Math.sin(φ - δ / 2)) * degrees);
						}
						sphereStream.lineEnd(), sphereStream.polygonEnd();
					};
					return rotateStream;
				};
				return p;
			}

			function gingeryIntegrate(f, a, b) {
				var n = 50,
					h = (b - a) / n,
					s = f(a) + f(b);
				for(var i = 1, x = a; i < n; ++i) s += 2 * f(x += h);
				return s * .5 * h;
			}
			(d3.geo.gingery = gingeryProjection).raw = gingery;

			function ginzburgPolyconic(a, b, c, d, e, f, g, h) {
				if(arguments.length < 8) h = 0;

				function forward(λ, φ) {
					if(!φ) return [a * λ / π, 0];
					var φ2 = φ * φ,
						xB = a + φ2 * (b + φ2 * (c + φ2 * d)),
						yB = φ * (e - 1 + φ2 * (f - h + φ2 * g)),
						m = (xB * xB + yB * yB) / (2 * yB),
						α = λ * Math.asin(xB / m) / π;
					return [m * Math.sin(α), φ * (1 + φ2 * h) + m * (1 - Math.cos(α))];
				}
				forward.invert = function(x, y) {
					var λ = π * x / a,
						φ = y,
						δλ, δφ, i = 50;
					do {
						var φ2 = φ * φ,
							xB = a + φ2 * (b + φ2 * (c + φ2 * d)),
							yB = φ * (e - 1 + φ2 * (f - h + φ2 * g)),
							p = xB * xB + yB * yB,
							q = 2 * yB,
							m = p / q,
							m2 = m * m,
							dαdλ = Math.asin(xB / m) / π,
							α = λ * dαdλ;
						xB2 = xB * xB, dxBdφ = (2 * b + φ2 * (4 * c + φ2 * 6 * d)) * φ, dyBdφ = e + φ2 * (3 * f + φ2 * 5 * g),
							dpdφ = 2 * (xB * dxBdφ + yB * (dyBdφ - 1)), dqdφ = 2 * (dyBdφ - 1), dmdφ = (dpdφ * q - p * dqdφ) / (q * q),
							cosα = Math.cos(α), sinα = Math.sin(α), mcosα = m * cosα, msinα = m * sinα, dαdφ = λ / π * (1 / asqrt(1 - xB2 / m2)) * (dxBdφ * m - xB * dmdφ) / m2,
							fx = msinα - x, fy = φ * (1 + φ2 * h) + m - mcosα - y, δxδφ = dmdφ * sinα + mcosα * dαdφ,
							δxδλ = mcosα * dαdλ, δyδφ = 1 + dmdφ - (dmdφ * cosα - msinα * dαdφ), δyδλ = msinα * dαdλ,
							denominator = δxδφ * δyδλ - δyδφ * δxδλ;
						if(!denominator) break;
						λ -= δλ = (fy * δxδφ - fx * δyδφ) / denominator;
						φ -= δφ = (fx * δyδλ - fy * δxδλ) / denominator;
					} while ((Math.abs(δλ) > ε || Math.abs(δφ) > ε) && --i > 0);
					return [λ, φ];
				};
				return forward;
			}
			var ginzburg4 = ginzburgPolyconic(2.8284, -1.6988, .75432, -.18071, 1.76003, -.38914, .042555);
			(d3.geo.ginzburg4 = function() {
				return projection(ginzburg4);
			}).raw = ginzburg4;
			var ginzburg5 = ginzburgPolyconic(2.583819, -.835827, .170354, -.038094, 1.543313, -.411435, .082742);
			(d3.geo.ginzburg5 = function() {
				return projection(ginzburg5);
			}).raw = ginzburg5;
			var ginzburg6 = ginzburgPolyconic(5 / 6 * π, -.62636, -.0344, 0, 1.3493, -.05524, 0, .045);
			(d3.geo.ginzburg6 = function() {
				return projection(ginzburg6);
			}).raw = ginzburg6;

			function ginzburg8(λ, φ) {
				var λ2 = λ * λ,
					φ2 = φ * φ;
				return [λ * (1 - .162388 * φ2) * (.87 - 952426e-9 * λ2 * λ2), φ * (1 + φ2 / 12)];
			}
			ginzburg8.invert = function(x, y) {
				var λ = x,
					φ = y,
					i = 50,
					δ;
				do {
					var φ2 = φ * φ;
					φ -= δ = (φ * (1 + φ2 / 12) - y) / (1 + φ2 / 4);
				} while (Math.abs(δ) > ε && --i > 0);
				i = 50;
				x /= 1 - .162388 * φ2;
				do {
					var λ4 = (λ4 = λ * λ) * λ4;
					λ -= δ = (λ * (.87 - 952426e-9 * λ4) - x) / (.87 - .00476213 * λ4);
				} while (Math.abs(δ) > ε && --i > 0);
				return [λ, φ];
			};
			(d3.geo.ginzburg8 = function() {
				return projection(ginzburg8);
			}).raw = ginzburg8;
			var ginzburg9 = ginzburgPolyconic(2.6516, -.76534, .19123, -.047094, 1.36289, -.13965, .031762);
			(d3.geo.ginzburg9 = function() {
				return projection(ginzburg9);
			}).raw = ginzburg9;

			function quincuncialProjection(projectHemisphere) {
				var dx = projectHemisphere(halfπ, 0)[0] - projectHemisphere(-halfπ, 0)[0];

				function projection() {
					var quincuncial = false,
						m = projectionMutator(projectAt),
						p = m(quincuncial);
					p.quincuncial = function(_) {
						if(!arguments.length) return quincuncial;
						return m(quincuncial = !!_);
					};
					return p;
				}

				function projectAt(quincuncial) {
					var forward = quincuncial ? function(λ, φ) {
						var t = Math.abs(λ) < halfπ,
							p = projectHemisphere(t ? λ : λ > 0 ? λ - π : λ + π, φ);
						var x = (p[0] - p[1]) * Math.SQRT1_2,
							y = (p[0] + p[1]) * Math.SQRT1_2;
						if(t) return [x, y];
						var d = dx * Math.SQRT1_2,
							s = x > 0 ^ y > 0 ? -1 : 1;
						return [s * x - sgn(y) * d, s * y - sgn(x) * d];
					} : function(λ, φ) {
						var s = λ > 0 ? -.5 : .5,
							point = projectHemisphere(λ + s * π, φ);
						point[0] -= s * dx;
						return point;
					};
					if(projectHemisphere.invert) forward.invert = quincuncial ? function(x0, y0) {
						var x = (x0 + y0) * Math.SQRT1_2,
							y = (y0 - x0) * Math.SQRT1_2,
							t = Math.abs(x) < .5 * dx && Math.abs(y) < .5 * dx;
						if(!t) {
							var d = dx * Math.SQRT1_2,
								s = x > 0 ^ y > 0 ? -1 : 1,
								x1 = -s * (x0 + (y > 0 ? 1 : -1) * d),
								y1 = -s * (y0 + (x > 0 ? 1 : -1) * d);
							x = (-x1 - y1) * Math.SQRT1_2;
							y = (x1 - y1) * Math.SQRT1_2;
						}
						var p = projectHemisphere.invert(x, y);
						if(!t) p[0] += x > 0 ? π : -π;
						return p;
					} : function(x, y) {
						var s = x > 0 ? -.5 : .5,
							location = projectHemisphere.invert(x + s * dx, y),
							λ = location[0] - s * π;
						if(λ < -π) λ += 2 * π;
						else if(λ > π) λ -= 2 * π;
						location[0] = λ;
						return location;
					};
					return forward;
				}
				projection.raw = projectAt;
				return projection;
			}

			function gringorten(λ, φ) {
				var sλ = sgn(λ),
					sφ = sgn(φ),
					cosφ = Math.cos(φ),
					x = Math.cos(λ) * cosφ,
					y = Math.sin(λ) * cosφ,
					z = Math.sin(sφ * φ);
				λ = Math.abs(Math.atan2(y, z));
				φ = asin(x);
				if(Math.abs(λ - halfπ) > ε) λ %= halfπ;
				var point = gringortenHexadecant(λ > π / 4 ? halfπ - λ : λ, φ);
				if(λ > π / 4) z = point[0], point[0] = -point[1], point[1] = -z;
				return point[0] *= sλ, point[1] *= -sφ, point;
			}
			gringorten.invert = function(x, y) {
				var sx = sgn(x),
					sy = sgn(y),
					x0 = -sx * x,
					y0 = -sy * y,
					t = y0 / x0 < 1,
					p = gringortenHexadecantInvert(t ? y0 : x0, t ? x0 : y0),
					λ = p[0],
					φ = p[1];
				if(t) λ = -halfπ - λ;
				var cosφ = Math.cos(φ),
					x = Math.cos(λ) * cosφ,
					y = Math.sin(λ) * cosφ,
					z = Math.sin(φ);
				return [sx * (Math.atan2(y, -z) + π), sy * asin(x)];
			};

			function gringortenHexadecant(λ, φ) {
				if(φ === halfπ) return [0, 0];
				var sinφ = Math.sin(φ),
					r = sinφ * sinφ,
					r2 = r * r,
					j = 1 + r2,
					k = 1 + 3 * r2,
					q = 1 - r2,
					z = asin(1 / Math.sqrt(j)),
					v = q + r * j * z,
					p2 = (1 - sinφ) / v,
					p = Math.sqrt(p2),
					a2 = p2 * j,
					a = Math.sqrt(a2),
					h = p * q;
				if(λ === 0) return [0, -(h + r * a)];
				var cosφ = Math.cos(φ),
					secφ = 1 / cosφ,
					drdφ = 2 * sinφ * cosφ,
					dvdφ = (-3 * r + z * k) * drdφ,
					dp2dφ = (-v * cosφ - (1 - sinφ) * dvdφ) / (v * v),
					dpdφ = .5 * dp2dφ / p,
					dhdφ = q * dpdφ - 2 * r * p * drdφ,
					dra2dφ = r * j * dp2dφ + p2 * k * drdφ,
					μ = -secφ * drdφ,
					ν = -secφ * dra2dφ,
					ζ = -2 * secφ * dhdφ,
					Λ = 4 * λ / π;
				if(λ > .222 * π || φ < π / 4 && λ > .175 * π) {
					var x = (h + r * asqrt(a2 * (1 + r2) - h * h)) / (1 + r2);
					if(λ > π / 4) return [x, x];
					var x1 = x,
						x0 = .5 * x,
						i = 50;
					x = .5 * (x0 + x1);
					do {
						var g = Math.sqrt(a2 - x * x),
							f = x * (ζ + μ * g) + ν * asin(x / a) - Λ;
						if(!f) break;
						if(f < 0) x0 = x;
						else x1 = x;
						x = .5 * (x0 + x1);
					} while (Math.abs(x1 - x0) > ε && --i > 0);
				} else {
					var x = ε,
						i = 25,
						δ;
					do {
						var x2 = x * x,
							g = asqrt(a2 - x2),
							ζμg = ζ + μ * g,
							f = x * ζμg + ν * asin(x / a) - Λ,
							df = ζμg + (ν - μ * x2) / g;
						x -= δ = g ? f / df : 0;
					} while (Math.abs(δ) > ε && --i > 0);
				}
				return [x, -h - r * asqrt(a2 - x * x)];
			}

			function gringortenHexadecantInvert(x, y) {
				var x0 = 0,
					x1 = 1,
					r = .5,
					i = 50;
				while(true) {
					var r2 = r * r,
						sinφ = Math.sqrt(r),
						z = Math.asin(1 / Math.sqrt(1 + r2)),
						v = 1 - r2 + r * (1 + r2) * z,
						p2 = (1 - sinφ) / v,
						p = Math.sqrt(p2),
						a2 = p2 * (1 + r2),
						h = p * (1 - r2),
						g2 = a2 - x * x,
						g = Math.sqrt(g2),
						y0 = y + h + r * g;
					if(Math.abs(x1 - x0) < ε2 || --i === 0 || y0 === 0) break;
					if(y0 > 0) x0 = r;
					else x1 = r;
					r = .5 * (x0 + x1);
				}
				if(!i) return null;
				var φ = Math.asin(sinφ),
					cosφ = Math.cos(φ),
					secφ = 1 / cosφ,
					drdφ = 2 * sinφ * cosφ,
					dvdφ = (-3 * r + z * (1 + 3 * r2)) * drdφ,
					dp2dφ = (-v * cosφ - (1 - sinφ) * dvdφ) / (v * v),
					dpdφ = .5 * dp2dφ / p,
					dhdφ = (1 - r2) * dpdφ - 2 * r * p * drdφ,
					ζ = -2 * secφ * dhdφ,
					μ = -secφ * drdφ,
					ν = -secφ * (r * (1 + r2) * dp2dφ + p2 * (1 + 3 * r2) * drdφ);
				return [π / 4 * (x * (ζ + μ * g) + ν * Math.asin(x / Math.sqrt(a2))), φ];
			}
			d3.geo.gringorten = quincuncialProjection(gringorten);

			function ellipticJi(u, v, m) {
				if(!u) {
					var b = ellipticJ(v, 1 - m);
					return [
						[0, b[0] / b[1]],
						[1 / b[1], 0],
						[b[2] / b[1], 0]
					];
				}
				var a = ellipticJ(u, m);
				if(!v) return [
					[a[0], 0],
					[a[1], 0],
					[a[2], 0]
				];
				var b = ellipticJ(v, 1 - m),
					denominator = b[1] * b[1] + m * a[0] * a[0] * b[0] * b[0];
				return [
					[a[0] * b[2] / denominator, a[1] * a[2] * b[0] * b[1] / denominator],
					[a[1] * b[1] / denominator, -a[0] * a[2] * b[0] * b[2] / denominator],
					[a[2] * b[1] * b[2] / denominator, -m * a[0] * a[1] * b[0] / denominator]
				];
			}

			function ellipticJ(u, m) {
				var ai, b, φ, t, twon;
				if(m < ε) {
					t = Math.sin(u);
					b = Math.cos(u);
					ai = .25 * m * (u - t * b);
					return [t - ai * b, b + ai * t, 1 - .5 * m * t * t, u - ai];
				}
				if(m >= 1 - ε) {
					ai = .25 * (1 - m);
					b = cosh(u);
					t = tanh(u);
					φ = 1 / b;
					twon = b * sinh(u);
					return [t + ai * (twon - u) / (b * b), φ - ai * t * φ * (twon - u), φ + ai * t * φ * (twon + u), 2 * Math.atan(Math.exp(u)) - halfπ + ai * (twon - u) / b];
				}
				var a = [1, 0, 0, 0, 0, 0, 0, 0, 0],
					c = [Math.sqrt(m), 0, 0, 0, 0, 0, 0, 0, 0],
					i = 0;
				b = Math.sqrt(1 - m);
				twon = 1;
				while(Math.abs(c[i] / a[i]) > ε && i < 8) {
					ai = a[i++];
					c[i] = .5 * (ai - b);
					a[i] = .5 * (ai + b);
					b = asqrt(ai * b);
					twon *= 2;
				}
				φ = twon * a[i] * u;
				do {
					t = c[i] * Math.sin(b = φ) / a[i];
					φ = .5 * (asin(t) + φ);
				} while (--i);
				return [Math.sin(φ), t = Math.cos(φ), t / Math.cos(φ - b), φ];
			}

			function ellipticFi(φ, ψ, m) {
				var r = Math.abs(φ),
					i = Math.abs(ψ),
					sinhψ = sinh(i);
				if(r) {
					var cscφ = 1 / Math.sin(r),
						cotφ2 = 1 / (Math.tan(r) * Math.tan(r)),
						b = -(cotφ2 + m * sinhψ * sinhψ * cscφ * cscφ - 1 + m),
						c = (m - 1) * cotφ2,
						cotλ2 = .5 * (-b + Math.sqrt(b * b - 4 * c));
					return [ellipticF(Math.atan(1 / Math.sqrt(cotλ2)), m) * sgn(φ), ellipticF(Math.atan(asqrt((cotλ2 / cotφ2 - 1) / m)), 1 - m) * sgn(ψ)];
				}
				return [0, ellipticF(Math.atan(sinhψ), 1 - m) * sgn(ψ)];
			}

			function ellipticF(φ, m) {
				if(!m) return φ;
				if(m === 1) return Math.log(Math.tan(φ / 2 + π / 4));
				var a = 1,
					b = Math.sqrt(1 - m),
					c = Math.sqrt(m);
				for(var i = 0; Math.abs(c) > ε; i++) {
					if(φ % π) {
						var dφ = Math.atan(b * Math.tan(φ) / a);
						if(dφ < 0) dφ += π;
						φ += dφ + ~~(φ / π) * π;
					} else φ += φ;
					c = (a + b) / 2;
					b = Math.sqrt(a * b);
					c = ((a = c) - b) / 2;
				}
				return φ / (Math.pow(2, i) * a);
			}

			function guyou(λ, φ) {
				var k_ = (Math.SQRT2 - 1) / (Math.SQRT2 + 1),
					k = Math.sqrt(1 - k_ * k_),
					K = ellipticF(halfπ, k * k),
					f = -1;
				var ψ = Math.log(Math.tan(π / 4 + Math.abs(φ) / 2)),
					r = Math.exp(f * ψ) / Math.sqrt(k_),
					at = guyouComplexAtan(r * Math.cos(f * λ), r * Math.sin(f * λ)),
					t = ellipticFi(at[0], at[1], k * k);
				return [-t[1], (φ >= 0 ? 1 : -1) * (.5 * K - t[0])];
			}

			function guyouComplexAtan(x, y) {
				var x2 = x * x,
					y_1 = y + 1,
					t = 1 - x2 - y * y;
				return [.5 * ((x >= 0 ? halfπ : -halfπ) - Math.atan2(t, 2 * x)), -.25 * Math.log(t * t + 4 * x2) + .5 * Math.log(y_1 * y_1 + x2)];
			}

			function guyouComplexDivide(a, b) {
				var denominator = b[0] * b[0] + b[1] * b[1];
				return [(a[0] * b[0] + a[1] * b[1]) / denominator, (a[1] * b[0] - a[0] * b[1]) / denominator];
			}
			guyou.invert = function(x, y) {
				var k_ = (Math.SQRT2 - 1) / (Math.SQRT2 + 1),
					k = Math.sqrt(1 - k_ * k_),
					K = ellipticF(halfπ, k * k),
					f = -1;
				var j = ellipticJi(.5 * K - y, -x, k * k),
					tn = guyouComplexDivide(j[0], j[1]),
					λ = Math.atan2(tn[1], tn[0]) / f;
				return [λ, 2 * Math.atan(Math.exp(.5 / f * Math.log(k_ * tn[0] * tn[0] + k_ * tn[1] * tn[1]))) - halfπ];
			};
			d3.geo.guyou = quincuncialProjection(guyou);

			function hammerRetroazimuthal(φ0) {
				var sinφ0 = Math.sin(φ0),
					cosφ0 = Math.cos(φ0),
					rotate = hammerRetroazimuthalRotation(φ0);
				rotate.invert = hammerRetroazimuthalRotation(-φ0);

				function forward(λ, φ) {
					var p = rotate(λ, φ);
					λ = p[0], φ = p[1];
					var sinφ = Math.sin(φ),
						cosφ = Math.cos(φ),
						cosλ = Math.cos(λ),
						z = acos(sinφ0 * sinφ + cosφ0 * cosφ * cosλ),
						sinz = Math.sin(z),
						K = Math.abs(sinz) > ε ? z / sinz : 1;
					return [K * cosφ0 * Math.sin(λ), (Math.abs(λ) > halfπ ? K : -K) * (sinφ0 * cosφ - cosφ0 * sinφ * cosλ)];
				}
				forward.invert = function(x, y) {
					var ρ = Math.sqrt(x * x + y * y),
						sinz = -Math.sin(ρ),
						cosz = Math.cos(ρ),
						a = ρ * cosz,
						b = -y * sinz,
						c = ρ * sinφ0,
						d = asqrt(a * a + b * b - c * c),
						φ = Math.atan2(a * c + b * d, b * c - a * d),
						λ = (ρ > halfπ ? -1 : 1) * Math.atan2(x * sinz, ρ * Math.cos(φ) * cosz + y * Math.sin(φ) * sinz);
					return rotate.invert(λ, φ);
				};
				return forward;
			}

			function hammerRetroazimuthalRotation(φ0) {
				var sinφ0 = Math.sin(φ0),
					cosφ0 = Math.cos(φ0);
				return function(λ, φ) {
					var cosφ = Math.cos(φ),
						x = Math.cos(λ) * cosφ,
						y = Math.sin(λ) * cosφ,
						z = Math.sin(φ);
					return [Math.atan2(y, x * cosφ0 - z * sinφ0), asin(z * cosφ0 + x * sinφ0)];
				};
			}

			function hammerRetroazimuthalProjection() {
				var φ0 = 0,
					m = projectionMutator(hammerRetroazimuthal),
					p = m(φ0),
					rotate_ = p.rotate,
					stream_ = p.stream,
					circle = d3.geo.circle();
				p.parallel = function(_) {
					if(!arguments.length) return φ0 / π * 180;
					var r = p.rotate();
					return m(φ0 = _ * π / 180).rotate(r);
				};
				p.rotate = function(_) {
					if(!arguments.length) return _ = rotate_.call(p), _[1] += φ0 / π * 180, _;
					rotate_.call(p, [_[0], _[1] - φ0 / π * 180]);
					circle.origin([-_[0], -_[1]]);
					return p;
				};
				p.stream = function(stream) {
					stream = stream_(stream);
					stream.sphere = function() {
						stream.polygonStart();
						var ε = .01,
							ring = circle.angle(90 - ε)().coordinates[0],
							n = ring.length - 1,
							i = -1,
							p;
						stream.lineStart();
						while(++i < n) stream.point((p = ring[i])[0], p[1]);
						stream.lineEnd();
						ring = circle.angle(90 + ε)().coordinates[0];
						n = ring.length - 1;
						stream.lineStart();
						while(--i >= 0) stream.point((p = ring[i])[0], p[1]);
						stream.lineEnd();
						stream.polygonEnd();
					};
					return stream;
				};
				return p;
			}
			(d3.geo.hammerRetroazimuthal = hammerRetroazimuthalProjection).raw = hammerRetroazimuthal;
			var hammerAzimuthalEqualArea = d3.geo.azimuthalEqualArea.raw;

			function hammer(A, B) {
				if(arguments.length < 2) B = A;
				if(B === 1) return hammerAzimuthalEqualArea;
				if(B === Infinity) return hammerQuarticAuthalic;

				function forward(λ, φ) {
					var coordinates = hammerAzimuthalEqualArea(λ / B, φ);
					coordinates[0] *= A;
					return coordinates;
				}
				forward.invert = function(x, y) {
					var coordinates = hammerAzimuthalEqualArea.invert(x / A, y);
					coordinates[0] *= B;
					return coordinates;
				};
				return forward;
			}

			function hammerProjection() {
				var B = 2,
					m = projectionMutator(hammer),
					p = m(B);
				p.coefficient = function(_) {
					if(!arguments.length) return B;
					return m(B = +_);
				};
				return p;
			}

			function hammerQuarticAuthalic(λ, φ) {
				return [λ * Math.cos(φ) / Math.cos(φ /= 2), 2 * Math.sin(φ)];
			}
			hammerQuarticAuthalic.invert = function(x, y) {
				var φ = 2 * asin(y / 2);
				return [x * Math.cos(φ / 2) / Math.cos(φ), φ];
			};
			(d3.geo.hammer = hammerProjection).raw = hammer;

			function hatano(λ, φ) {
				var c = Math.sin(φ) * (φ < 0 ? 2.43763 : 2.67595);
				for(var i = 0, δ; i < 20; i++) {
					φ -= δ = (φ + Math.sin(φ) - c) / (1 + Math.cos(φ));
					if(Math.abs(δ) < ε) break;
				}
				return [.85 * λ * Math.cos(φ *= .5), Math.sin(φ) * (φ < 0 ? 1.93052 : 1.75859)];
			}
			hatano.invert = function(x, y) {
				var θ = Math.abs(θ = y * (y < 0 ? .5179951515653813 : .5686373742600607)) > 1 - ε ? θ > 0 ? halfπ : -halfπ : asin(θ);
				return [1.1764705882352942 * x / Math.cos(θ), Math.abs(θ = ((θ += θ) + Math.sin(θ)) * (y < 0 ? .4102345310814193 : .3736990601468637)) > 1 - ε ? θ > 0 ? halfπ : -halfπ : asin(θ)];
			};
			(d3.geo.hatano = function() {
				return projection(hatano);
			}).raw = hatano;
			var healpixParallel = 41 + 48 / 36 + 37 / 3600;

			function healpix(h) {
				var lambert = d3.geo.cylindricalEqualArea.raw(0),
					φ0 = healpixParallel * π / 180,
					dx0 = 2 * π,
					dx1 = d3.geo.collignon.raw(π, φ0)[0] - d3.geo.collignon.raw(-π, φ0)[0],
					y0 = lambert(0, φ0)[1],
					y1 = d3.geo.collignon.raw(0, φ0)[1],
					dy1 = d3.geo.collignon.raw(0, halfπ)[1] - y1,
					k = 2 * π / h;

				function forward(λ, φ) {
					var point, φ2 = Math.abs(φ);
					if(φ2 > φ0) {
						var i = Math.min(h - 1, Math.max(0, Math.floor((λ + π) / k)));
						λ += π * (h - 1) / h - i * k;
						point = d3.geo.collignon.raw(λ, φ2);
						point[0] = point[0] * dx0 / dx1 - dx0 * (h - 1) / (2 * h) + i * dx0 / h;
						point[1] = y0 + (point[1] - y1) * 4 * dy1 / dx0;
						if(φ < 0) point[1] = -point[1];
					} else {
						point = lambert(λ, φ);
					}
					point[0] /= 2;
					return point;
				}
				forward.invert = function(x, y) {
					x *= 2;
					var y2 = Math.abs(y);
					if(y2 > y0) {
						var i = Math.min(h - 1, Math.max(0, Math.floor((x + π) / k)));
						x = (x + π * (h - 1) / h - i * k) * dx1 / dx0;
						var point = d3.geo.collignon.raw.invert(x, .25 * (y2 - y0) * dx0 / dy1 + y1);
						point[0] -= π * (h - 1) / h - i * k;
						if(y < 0) point[1] = -point[1];
						return point;
					}
					return lambert.invert(x, y);
				};
				return forward;
			}

			function healpixProjection() {
				var n = 2,
					m = projectionMutator(healpix),
					p = m(n),
					stream_ = p.stream;
				p.lobes = function(_) {
					if(!arguments.length) return n;
					return m(n = +_);
				};
				p.stream = function(stream) {
					var rotate = p.rotate(),
						rotateStream = stream_(stream),
						sphereStream = (p.rotate([0, 0]),
							stream_(stream));
					p.rotate(rotate);
					rotateStream.sphere = function() {
						d3.geo.stream(sphere(), sphereStream);
					};
					return rotateStream;
				};

				function sphere() {
					var step = 180 / n;
					return {
						type: "Polygon",
						coordinates: [d3.range(-180, 180 + step / 2, step).map(function(x, i) {
							return [x, i & 1 ? 90 - 1e-6 : healpixParallel];
						}).concat(d3.range(180, -180 - step / 2, -step).map(function(x, i) {
							return [x, i & 1 ? -90 + 1e-6 : -healpixParallel];
						}))]
					};
				}
				return p;
			}
			(d3.geo.healpix = healpixProjection).raw = healpix;

			function hill(K) {
				var L = 1 + K,
					sinβ = Math.sin(1 / L),
					β = asin(sinβ),
					A = 2 * Math.sqrt(π / (B = π + 4 * β * L)),
					B, ρ0 = .5 * A * (L + Math.sqrt(K * (2 + K))),
					K2 = K * K,
					L2 = L * L;

				function forward(λ, φ) {
					var t = 1 - Math.sin(φ),
						ρ, ω;
					if(t && t < 2) {
						var θ = halfπ - φ,
							i = 25,
							δ;
						do {
							var sinθ = Math.sin(θ),
								cosθ = Math.cos(θ),
								β_β1 = β + Math.atan2(sinθ, L - cosθ),
								C = 1 + L2 - 2 * L * cosθ;
							θ -= δ = (θ - K2 * β - L * sinθ + C * β_β1 - .5 * t * B) / (2 * L * sinθ * β_β1);
						} while (Math.abs(δ) > ε2 && --i > 0);
						ρ = A * Math.sqrt(C);
						ω = λ * β_β1 / π;
					} else {
						ρ = A * (K + t);
						ω = λ * β / π;
					}
					return [ρ * Math.sin(ω), ρ0 - ρ * Math.cos(ω)];
				}
				forward.invert = function(x, y) {
					var ρ2 = x * x + (y -= ρ0) * y,
						cosθ = (1 + L2 - ρ2 / (A * A)) / (2 * L),
						θ = acos(cosθ),
						sinθ = Math.sin(θ),
						β_β1 = β + Math.atan2(sinθ, L - cosθ);
					return [asin(x / Math.sqrt(ρ2)) * π / β_β1, asin(1 - 2 * (θ - K2 * β - L * sinθ + (1 + L2 - 2 * L * cosθ) * β_β1) / B)];
				};
				return forward;
			}

			function hillProjection() {
				var K = 1,
					m = projectionMutator(hill),
					p = m(K);
				p.ratio = function(_) {
					if(!arguments.length) return K;
					return m(K = +_);
				};
				return p;
			}
			(d3.geo.hill = hillProjection).raw = hill;
			var sinuMollweideφ = .7109889596207567,
				sinuMollweideY = .0528035274542;

			function sinuMollweide(λ, φ) {
				return φ > -sinuMollweideφ ? (λ = mollweide(λ, φ), λ[1] += sinuMollweideY, λ) : sinusoidal(λ, φ);
			}
			sinuMollweide.invert = function(x, y) {
				return y > -sinuMollweideφ ? mollweide.invert(x, y - sinuMollweideY) : sinusoidal.invert(x, y);
			};
			(d3.geo.sinuMollweide = function() {
				return projection(sinuMollweide).rotate([-20, -55]);
			}).raw = sinuMollweide;

			function homolosine(λ, φ) {
				return Math.abs(φ) > sinuMollweideφ ? (λ = mollweide(λ, φ), λ[1] -= φ > 0 ? sinuMollweideY : -sinuMollweideY,
					λ) : sinusoidal(λ, φ);
			}
			homolosine.invert = function(x, y) {
				return Math.abs(y) > sinuMollweideφ ? mollweide.invert(x, y + (y > 0 ? sinuMollweideY : -sinuMollweideY)) : sinusoidal.invert(x, y);
			};
			(d3.geo.homolosine = function() {
				return projection(homolosine);
			}).raw = homolosine;

			function kavrayskiy7(λ, φ) {
				return [3 * λ / (2 * π) * Math.sqrt(π * π / 3 - φ * φ), φ];
			}
			kavrayskiy7.invert = function(x, y) {
				return [2 / 3 * π * x / Math.sqrt(π * π / 3 - y * y), y];
			};
			(d3.geo.kavrayskiy7 = function() {
				return projection(kavrayskiy7);
			}).raw = kavrayskiy7;

			function lagrange(n) {
				function forward(λ, φ) {
					if(Math.abs(Math.abs(φ) - halfπ) < ε) return [0, φ < 0 ? -2 : 2];
					var sinφ = Math.sin(φ),
						v = Math.pow((1 + sinφ) / (1 - sinφ), n / 2),
						c = .5 * (v + 1 / v) + Math.cos(λ *= n);
					return [2 * Math.sin(λ) / c, (v - 1 / v) / c];
				}
				forward.invert = function(x, y) {
					var y0 = Math.abs(y);
					if(Math.abs(y0 - 2) < ε) return x ? null : [0, sgn(y) * halfπ];
					if(y0 > 2) return null;
					x /= 2, y /= 2;
					var x2 = x * x,
						y2 = y * y,
						t = 2 * y / (1 + x2 + y2);
					t = Math.pow((1 + t) / (1 - t), 1 / n);
					return [Math.atan2(2 * x, 1 - x2 - y2) / n, asin((t - 1) / (t + 1))];
				};
				return forward;
			}

			function lagrangeProjection() {
				var n = .5,
					m = projectionMutator(lagrange),
					p = m(n);
				p.spacing = function(_) {
					if(!arguments.length) return n;
					return m(n = +_);
				};
				return p;
			}
			(d3.geo.lagrange = lagrangeProjection).raw = lagrange;

			function larrivee(λ, φ) {
				return [λ * (1 + Math.sqrt(Math.cos(φ))) / 2, φ / (Math.cos(φ / 2) * Math.cos(λ / 6))];
			}
			larrivee.invert = function(x, y) {
				var x0 = Math.abs(x),
					y0 = Math.abs(y),
					π_sqrt2 = π / Math.SQRT2,
					λ = ε,
					φ = halfπ;
				if(y0 < π_sqrt2) φ *= y0 / π_sqrt2;
				else λ += 6 * acos(π_sqrt2 / y0);
				for(var i = 0; i < 25; i++) {
					var sinφ = Math.sin(φ),
						sqrtcosφ = asqrt(Math.cos(φ)),
						sinφ_2 = Math.sin(φ / 2),
						cosφ_2 = Math.cos(φ / 2),
						sinλ_6 = Math.sin(λ / 6),
						cosλ_6 = Math.cos(λ / 6),
						f0 = .5 * λ * (1 + sqrtcosφ) - x0,
						f1 = φ / (cosφ_2 * cosλ_6) - y0,
						df0dφ = sqrtcosφ ? -.25 * λ * sinφ / sqrtcosφ : 0,
						df0dλ = .5 * (1 + sqrtcosφ),
						df1dφ = (1 + .5 * φ * sinφ_2 / cosφ_2) / (cosφ_2 * cosλ_6),
						df1dλ = φ / cosφ_2 * (sinλ_6 / 6) / (cosλ_6 * cosλ_6),
						denom = df0dφ * df1dλ - df1dφ * df0dλ,
						dφ = (f0 * df1dλ - f1 * df0dλ) / denom,
						dλ = (f1 * df0dφ - f0 * df1dφ) / denom;
					φ -= dφ;
					λ -= dλ;
					if(Math.abs(dφ) < ε && Math.abs(dλ) < ε) break;
				}
				return [x < 0 ? -λ : λ, y < 0 ? -φ : φ];
			};
			(d3.geo.larrivee = function() {
				return projection(larrivee);
			}).raw = larrivee;

			function laskowski(λ, φ) {
				var λ2 = λ * λ,
					φ2 = φ * φ;
				return [λ * (.975534 + φ2 * (-.119161 + λ2 * -.0143059 + φ2 * -.0547009)), φ * (1.00384 + λ2 * (.0802894 + φ2 * -.02855 + λ2 * 199025e-9) + φ2 * (.0998909 + φ2 * -.0491032))];
			}
			laskowski.invert = function(x, y) {
				var λ = sgn(x) * π,
					φ = y / 2,
					i = 50;
				do {
					var λ2 = λ * λ,
						φ2 = φ * φ,
						λφ = λ * φ,
						fx = λ * (.975534 + φ2 * (-.119161 + λ2 * -.0143059 + φ2 * -.0547009)) - x,
						fy = φ * (1.00384 + λ2 * (.0802894 + φ2 * -.02855 + λ2 * 199025e-9) + φ2 * (.0998909 + φ2 * -.0491032)) - y,
						δxδλ = .975534 - φ2 * (.119161 + 3 * λ2 * .0143059 + φ2 * .0547009),
						δxδφ = -λφ * (2 * .119161 + 4 * .0547009 * φ2 + 2 * .0143059 * λ2),
						δyδλ = λφ * (2 * .0802894 + 4 * 199025e-9 * λ2 + 2 * -.02855 * φ2),
						δyδφ = 1.00384 + λ2 * (.0802894 + 199025e-9 * λ2) + φ2 * (3 * (.0998909 - .02855 * λ2) - 5 * .0491032 * φ2),
						denominator = δxδφ * δyδλ - δyδφ * δxδλ,
						δλ = (fy * δxδφ - fx * δyδφ) / denominator,
						δφ = (fx * δyδλ - fy * δxδλ) / denominator;
					λ -= δλ, φ -= δφ;
				} while ((Math.abs(δλ) > ε || Math.abs(δφ) > ε) && --i > 0);
				return i && [λ, φ];
			};
			(d3.geo.laskowski = function() {
				return projection(laskowski);
			}).raw = laskowski;

			function littrow(λ, φ) {
				return [Math.sin(λ) / Math.cos(φ), Math.tan(φ) * Math.cos(λ)];
			}
			littrow.invert = function(x, y) {
				var x2 = x * x,
					y2 = y * y,
					y2_1 = y2 + 1,
					cosφ = x ? Math.SQRT1_2 * Math.sqrt((y2_1 - Math.sqrt(x2 * x2 + 2 * x2 * (y2 - 1) + y2_1 * y2_1)) / x2 + 1) : 1 / Math.sqrt(y2_1);
				return [asin(x * cosφ), sgn(y) * acos(cosφ)];
			};
			(d3.geo.littrow = function() {
				return projection(littrow);
			}).raw = littrow;

			function loximuthal(φ0) {
				var cosφ0 = Math.cos(φ0),
					tanφ0 = Math.tan(π / 4 + φ0 / 2);

				function forward(λ, φ) {
					var y = φ - φ0,
						x = Math.abs(y) < ε ? λ * cosφ0 : Math.abs(x = π / 4 + φ / 2) < ε || Math.abs(Math.abs(x) - halfπ) < ε ? 0 : λ * y / Math.log(Math.tan(x) / tanφ0);
					return [x, y];
				}
				forward.invert = function(x, y) {
					var λ, φ = y + φ0;
					return [Math.abs(y) < ε ? x / cosφ0 : Math.abs(λ = π / 4 + φ / 2) < ε || Math.abs(Math.abs(λ) - halfπ) < ε ? 0 : x * Math.log(Math.tan(λ) / tanφ0) / y, φ];
				};
				return forward;
			}
			(d3.geo.loximuthal = function() {
				return parallel1Projection(loximuthal).parallel(40);
			}).raw = loximuthal;

			function miller(λ, φ) {
				return [λ, 1.25 * Math.log(Math.tan(π / 4 + .4 * φ))];
			}
			miller.invert = function(x, y) {
				return [x, 2.5 * Math.atan(Math.exp(.8 * y)) - .625 * π];
			};
			(d3.geo.miller = function() {
				return projection(miller);
			}).raw = miller;

			function modifiedStereographic(C) {
				var m = C.length - 1;

				function forward(λ, φ) {
					var cosφ = Math.cos(φ),
						k = 2 / (1 + cosφ * Math.cos(λ)),
						zr = k * cosφ * Math.sin(λ),
						zi = k * Math.sin(φ),
						i = m,
						w = C[i],
						ar = w[0],
						ai = w[1],
						t;
					while(--i >= 0) {
						w = C[i];
						ar = w[0] + zr * (t = ar) - zi * ai;
						ai = w[1] + zr * ai + zi * t;
					}
					ar = zr * (t = ar) - zi * ai;
					ai = zr * ai + zi * t;
					return [ar, ai];
				}
				forward.invert = function(x, y) {
					var i = 20,
						zr = x,
						zi = y;
					do {
						var j = m,
							w = C[j],
							ar = w[0],
							ai = w[1],
							br = 0,
							bi = 0,
							t;
						while(--j >= 0) {
							w = C[j];
							br = ar + zr * (t = br) - zi * bi;
							bi = ai + zr * bi + zi * t;
							ar = w[0] + zr * (t = ar) - zi * ai;
							ai = w[1] + zr * ai + zi * t;
						}
						br = ar + zr * (t = br) - zi * bi;
						bi = ai + zr * bi + zi * t;
						ar = zr * (t = ar) - zi * ai - x;
						ai = zr * ai + zi * t - y;
						var denominator = br * br + bi * bi,
							δr, δi;
						zr -= δr = (ar * br + ai * bi) / denominator;
						zi -= δi = (ai * br - ar * bi) / denominator;
					} while (Math.abs(δr) + Math.abs(δi) > ε * ε && --i > 0);
					if(i) {
						var ρ = Math.sqrt(zr * zr + zi * zi),
							c = 2 * Math.atan(ρ * .5),
							sinc = Math.sin(c);
						return [Math.atan2(zr * sinc, ρ * Math.cos(c)), ρ ? asin(zi * sinc / ρ) : 0];
					}
				};
				return forward;
			}
			var modifiedStereographicCoefficients = {
				alaska: [
					[.9972523, 0],
					[.0052513, -.0041175],
					[.0074606, .0048125],
					[-.0153783, -.1968253],
					[.0636871, -.1408027],
					[.3660976, -.2937382]
				],
				gs48: [
					[.98879, 0],
					[0, 0],
					[-.050909, 0],
					[0, 0],
					[.075528, 0]
				],
				gs50: [
					[.984299, 0],
					[.0211642, .0037608],
					[-.1036018, -.0575102],
					[-.0329095, -.0320119],
					[.0499471, .1223335],
					[.026046, .0899805],
					[7388e-7, -.1435792],
					[.0075848, -.1334108],
					[-.0216473, .0776645],
					[-.0225161, .0853673]
				],
				miller: [
					[.9245, 0],
					[0, 0],
					[.01943, 0]
				],
				lee: [
					[.721316, 0],
					[0, 0],
					[-.00881625, -.00617325]
				]
			};

			function modifiedStereographicProjection() {
				var coefficients = modifiedStereographicCoefficients.miller,
					m = projectionMutator(modifiedStereographic),
					p = m(coefficients);
				p.coefficients = function(_) {
					if(!arguments.length) return coefficients;
					return m(coefficients = typeof _ === "string" ? modifiedStereographicCoefficients[_] : _);
				};
				return p;
			}
			(d3.geo.modifiedStereographic = modifiedStereographicProjection).raw = modifiedStereographic;

			function mtFlatPolarParabolic(λ, φ) {
				var sqrt6 = Math.sqrt(6),
					sqrt7 = Math.sqrt(7),
					θ = Math.asin(7 * Math.sin(φ) / (3 * sqrt6));
				return [sqrt6 * λ * (2 * Math.cos(2 * θ / 3) - 1) / sqrt7, 9 * Math.sin(θ / 3) / sqrt7];
			}
			mtFlatPolarParabolic.invert = function(x, y) {
				var sqrt6 = Math.sqrt(6),
					sqrt7 = Math.sqrt(7),
					θ = 3 * asin(y * sqrt7 / 9);
				return [x * sqrt7 / (sqrt6 * (2 * Math.cos(2 * θ / 3) - 1)), asin(Math.sin(θ) * 3 * sqrt6 / 7)];
			};
			(d3.geo.mtFlatPolarParabolic = function() {
				return projection(mtFlatPolarParabolic);
			}).raw = mtFlatPolarParabolic;

			function mtFlatPolarQuartic(λ, φ) {
				var k = (1 + Math.SQRT1_2) * Math.sin(φ),
					θ = φ;
				for(var i = 0, δ; i < 25; i++) {
					θ -= δ = (Math.sin(θ / 2) + Math.sin(θ) - k) / (.5 * Math.cos(θ / 2) + Math.cos(θ));
					if(Math.abs(δ) < ε) break;
				}
				return [λ * (1 + 2 * Math.cos(θ) / Math.cos(θ / 2)) / (3 * Math.SQRT2), 2 * Math.sqrt(3) * Math.sin(θ / 2) / Math.sqrt(2 + Math.SQRT2)];
			}
			mtFlatPolarQuartic.invert = function(x, y) {
				var sinθ_2 = y * Math.sqrt(2 + Math.SQRT2) / (2 * Math.sqrt(3)),
					θ = 2 * asin(sinθ_2);
				return [3 * Math.SQRT2 * x / (1 + 2 * Math.cos(θ) / Math.cos(θ / 2)), asin((sinθ_2 + Math.sin(θ)) / (1 + Math.SQRT1_2))];
			};
			(d3.geo.mtFlatPolarQuartic = function() {
				return projection(mtFlatPolarQuartic);
			}).raw = mtFlatPolarQuartic;

			function mtFlatPolarSinusoidal(λ, φ) {
				var A = Math.sqrt(6 / (4 + π)),
					k = (1 + π / 4) * Math.sin(φ),
					θ = φ / 2;
				for(var i = 0, δ; i < 25; i++) {
					θ -= δ = (θ / 2 + Math.sin(θ) - k) / (.5 + Math.cos(θ));
					if(Math.abs(δ) < ε) break;
				}
				return [A * (.5 + Math.cos(θ)) * λ / 1.5, A * θ];
			}
			mtFlatPolarSinusoidal.invert = function(x, y) {
				var A = Math.sqrt(6 / (4 + π)),
					θ = y / A;
				if(Math.abs(Math.abs(θ) - halfπ) < ε) θ = θ < 0 ? -halfπ : halfπ;
				return [1.5 * x / (A * (.5 + Math.cos(θ))), asin((θ / 2 + Math.sin(θ)) / (1 + π / 4))];
			};
			(d3.geo.mtFlatPolarSinusoidal = function() {
				return projection(mtFlatPolarSinusoidal);
			}).raw = mtFlatPolarSinusoidal;

			function naturalEarth(λ, φ) {
				var φ2 = φ * φ,
					φ4 = φ2 * φ2;
				return [λ * (.8707 - .131979 * φ2 + φ4 * (-.013791 + φ4 * (.003971 * φ2 - .001529 * φ4))), φ * (1.007226 + φ2 * (.015085 + φ4 * (-.044475 + .028874 * φ2 - .005916 * φ4)))];
			}
			naturalEarth.invert = function(x, y) {
				var φ = y,
					i = 25,
					δ;
				do {
					var φ2 = φ * φ,
						φ4 = φ2 * φ2;
					φ -= δ = (φ * (1.007226 + φ2 * (.015085 + φ4 * (-.044475 + .028874 * φ2 - .005916 * φ4))) - y) / (1.007226 + φ2 * (.015085 * 3 + φ4 * (-.044475 * 7 + .028874 * 9 * φ2 - .005916 * 11 * φ4)));
				} while (Math.abs(δ) > ε && --i > 0);
				return [x / (.8707 + (φ2 = φ * φ) * (-.131979 + φ2 * (-.013791 + φ2 * φ2 * φ2 * (.003971 - .001529 * φ2)))), φ];
			};
			(d3.geo.naturalEarth = function() {
				return projection(naturalEarth);
			}).raw = naturalEarth;

			function nellHammer(λ, φ) {
				return [λ * (1 + Math.cos(φ)) / 2, 2 * (φ - Math.tan(φ / 2))];
			}
			nellHammer.invert = function(x, y) {
				var p = y / 2;
				for(var i = 0, δ = Infinity; i < 10 && Math.abs(δ) > ε; i++) {
					var c = Math.cos(y / 2);
					y -= δ = (y - Math.tan(y / 2) - p) / (1 - .5 / (c * c));
				}
				return [2 * x / (1 + Math.cos(y)), y];
			};
			(d3.geo.nellHammer = function() {
				return projection(nellHammer);
			}).raw = nellHammer;
			var pattersonK1 = 1.0148,
				pattersonK2 = .23185,
				pattersonK3 = -.14499,
				pattersonK4 = .02406,
				pattersonC1 = pattersonK1,
				pattersonC2 = 5 * pattersonK2,
				pattersonC3 = 7 * pattersonK3,
				pattersonC4 = 9 * pattersonK4,
				pattersonYmax = 1.790857183;

			function patterson(λ, φ) {
				var φ2 = φ * φ;
				return [λ, φ * (pattersonK1 + φ2 * φ2 * (pattersonK2 + φ2 * (pattersonK3 + pattersonK4 * φ2)))];
			}
			patterson.invert = function(x, y) {
				if(y > pattersonYmax) y = pattersonYmax;
				else if(y < -pattersonYmax) y = -pattersonYmax;
				var yc = y,
					δ;
				do {
					var y2 = yc * yc;
					yc -= δ = (yc * (pattersonK1 + y2 * y2 * (pattersonK2 + y2 * (pattersonK3 + pattersonK4 * y2))) - y) / (pattersonC1 + y2 * y2 * (pattersonC2 + y2 * (pattersonC3 + pattersonC4 * y2)));
				} while (Math.abs(δ) > ε);
				return [x, yc];
			};
			(d3.geo.patterson = function() {
				return projection(patterson);
			}).raw = patterson;
			var peirceQuincuncialProjection = quincuncialProjection(guyou);
			(d3.geo.peirceQuincuncial = function() {
				return peirceQuincuncialProjection().quincuncial(true).rotate([-90, -90, 45]).clipAngle(180 - 1e-6);
			}).raw = peirceQuincuncialProjection.raw;

			function polyconic(λ, φ) {
				if(Math.abs(φ) < ε) return [λ, 0];
				var tanφ = Math.tan(φ),
					k = λ * Math.sin(φ);
				return [Math.sin(k) / tanφ, φ + (1 - Math.cos(k)) / tanφ];
			}
			polyconic.invert = function(x, y) {
				if(Math.abs(y) < ε) return [x, 0];
				var k = x * x + y * y,
					φ = y * .5,
					i = 10,
					δ;
				do {
					var tanφ = Math.tan(φ),
						secφ = 1 / Math.cos(φ),
						j = k - 2 * y * φ + φ * φ;
					φ -= δ = (tanφ * j + 2 * (φ - y)) / (2 + j * secφ * secφ + 2 * (φ - y) * tanφ);
				} while (Math.abs(δ) > ε && --i > 0);
				tanφ = Math.tan(φ);
				return [(Math.abs(y) < Math.abs(φ + 1 / tanφ) ? asin(x * tanφ) : sgn(x) * (acos(Math.abs(x * tanφ)) + halfπ)) / Math.sin(φ), φ];
			};
			(d3.geo.polyconic = function() {
				return projection(polyconic);
			}).raw = polyconic;

			function rectangularPolyconic(φ0) {
				var sinφ0 = Math.sin(φ0);

				function forward(λ, φ) {
					var A = sinφ0 ? Math.tan(λ * sinφ0 / 2) / sinφ0 : λ / 2;
					if(!φ) return [2 * A, -φ0];
					var E = 2 * Math.atan(A * Math.sin(φ)),
						cotφ = 1 / Math.tan(φ);
					return [Math.sin(E) * cotφ, φ + (1 - Math.cos(E)) * cotφ - φ0];
				}
				forward.invert = function(x, y) {
					if(Math.abs(y += φ0) < ε) return [sinφ0 ? 2 * Math.atan(sinφ0 * x / 2) / sinφ0 : x, 0];
					var k = x * x + y * y,
						φ = 0,
						i = 10,
						δ;
					do {
						var tanφ = Math.tan(φ),
							secφ = 1 / Math.cos(φ),
							j = k - 2 * y * φ + φ * φ;
						φ -= δ = (tanφ * j + 2 * (φ - y)) / (2 + j * secφ * secφ + 2 * (φ - y) * tanφ);
					} while (Math.abs(δ) > ε && --i > 0);
					var E = x * (tanφ = Math.tan(φ)),
						A = Math.tan(Math.abs(y) < Math.abs(φ + 1 / tanφ) ? asin(E) * .5 : acos(E) * .5 + π / 4) / Math.sin(φ);
					return [sinφ0 ? 2 * Math.atan(sinφ0 * A) / sinφ0 : 2 * A, φ];
				};
				return forward;
			}
			(d3.geo.rectangularPolyconic = function() {
				return parallel1Projection(rectangularPolyconic);
			}).raw = rectangularPolyconic;
			var robinsonConstants = [
				[.9986, -.062],
				[1, 0],
				[.9986, .062],
				[.9954, .124],
				[.99, .186],
				[.9822, .248],
				[.973, .31],
				[.96, .372],
				[.9427, .434],
				[.9216, .4958],
				[.8962, .5571],
				[.8679, .6176],
				[.835, .6769],
				[.7986, .7346],
				[.7597, .7903],
				[.7186, .8435],
				[.6732, .8936],
				[.6213, .9394],
				[.5722, .9761],
				[.5322, 1]
			];
			robinsonConstants.forEach(function(d) {
				d[1] *= 1.0144;
			});

			function robinson(λ, φ) {
				var i = Math.min(18, Math.abs(φ) * 36 / π),
					i0 = Math.floor(i),
					di = i - i0,
					ax = (k = robinsonConstants[i0])[0],
					ay = k[1],
					bx = (k = robinsonConstants[++i0])[0],
					by = k[1],
					cx = (k = robinsonConstants[Math.min(19, ++i0)])[0],
					cy = k[1],
					k;
				return [λ * (bx + di * (cx - ax) / 2 + di * di * (cx - 2 * bx + ax) / 2), (φ > 0 ? halfπ : -halfπ) * (by + di * (cy - ay) / 2 + di * di * (cy - 2 * by + ay) / 2)];
			}
			robinson.invert = function(x, y) {
				var yy = y / halfπ,
					φ = yy * 90,
					i = Math.min(18, Math.abs(φ / 5)),
					i0 = Math.max(0, Math.floor(i));
				do {
					var ay = robinsonConstants[i0][1],
						by = robinsonConstants[i0 + 1][1],
						cy = robinsonConstants[Math.min(19, i0 + 2)][1],
						u = cy - ay,
						v = cy - 2 * by + ay,
						t = 2 * (Math.abs(yy) - by) / u,
						c = v / u,
						di = t * (1 - c * t * (1 - 2 * c * t));
					if(di >= 0 || i0 === 1) {
						φ = (y >= 0 ? 5 : -5) * (di + i);
						var j = 50,
							δ;
						do {
							i = Math.min(18, Math.abs(φ) / 5);
							i0 = Math.floor(i);
							di = i - i0;
							ay = robinsonConstants[i0][1];
							by = robinsonConstants[i0 + 1][1];
							cy = robinsonConstants[Math.min(19, i0 + 2)][1];
							φ -= (δ = (y >= 0 ? halfπ : -halfπ) * (by + di * (cy - ay) / 2 + di * di * (cy - 2 * by + ay) / 2) - y) * degrees;
						} while (Math.abs(δ) > ε2 && --j > 0);
						break;
					}
				} while (--i0 >= 0);
				var ax = robinsonConstants[i0][0],
					bx = robinsonConstants[i0 + 1][0],
					cx = robinsonConstants[Math.min(19, i0 + 2)][0];
				return [x / (bx + di * (cx - ax) / 2 + di * di * (cx - 2 * bx + ax) / 2), φ * radians];
			};
			(d3.geo.robinson = function() {
				return projection(robinson);
			}).raw = robinson;

			function satelliteVertical(P) {
				function forward(λ, φ) {
					var cosφ = Math.cos(φ),
						k = (P - 1) / (P - cosφ * Math.cos(λ));
					return [k * cosφ * Math.sin(λ), k * Math.sin(φ)];
				}
				forward.invert = function(x, y) {
					var ρ2 = x * x + y * y,
						ρ = Math.sqrt(ρ2),
						sinc = (P - Math.sqrt(1 - ρ2 * (P + 1) / (P - 1))) / ((P - 1) / ρ + ρ / (P - 1));
					return [Math.atan2(x * sinc, ρ * Math.sqrt(1 - sinc * sinc)), ρ ? asin(y * sinc / ρ) : 0];
				};
				return forward;
			}

			function satellite(P, ω) {
				var vertical = satelliteVertical(P);
				if(!ω) return vertical;
				var cosω = Math.cos(ω),
					sinω = Math.sin(ω);

				function forward(λ, φ) {
					var coordinates = vertical(λ, φ),
						y = coordinates[1],
						A = y * sinω / (P - 1) + cosω;
					return [coordinates[0] * cosω / A, y / A];
				}
				forward.invert = function(x, y) {
					var k = (P - 1) / (P - 1 - y * sinω);
					return vertical.invert(k * x, k * y * cosω);
				};
				return forward;
			}

			function satelliteProjection() {
				var P = 1.4,
					ω = 0,
					m = projectionMutator(satellite),
					p = m(P, ω);
				p.distance = function(_) {
					if(!arguments.length) return P;
					return m(P = +_, ω);
				};
				p.tilt = function(_) {
					if(!arguments.length) return ω * 180 / π;
					return m(P, ω = _ * π / 180);
				};
				return p;
			}
			(d3.geo.satellite = satelliteProjection).raw = satellite;

			function times(λ, φ) {
				var t = Math.tan(φ / 2),
					s = Math.sin(π / 4 * t);
				return [λ * (.74482 - .34588 * s * s), 1.70711 * t];
			}
			times.invert = function(x, y) {
				var t = y / 1.70711,
					s = Math.sin(π / 4 * t);
				return [x / (.74482 - .34588 * s * s), 2 * Math.atan(t)];
			};
			(d3.geo.times = function() {
				return projection(times);
			}).raw = times;

			function twoPointEquidistant(z0) {
				if(!z0) return d3.geo.azimuthalEquidistant.raw;
				var λa = -z0 / 2,
					λb = -λa,
					z02 = z0 * z0,
					tanλ0 = Math.tan(λb),
					S = .5 / Math.sin(λb);

				function forward(λ, φ) {
					var za = acos(Math.cos(φ) * Math.cos(λ - λa)),
						zb = acos(Math.cos(φ) * Math.cos(λ - λb)),
						ys = φ < 0 ? -1 : 1;
					za *= za, zb *= zb;
					return [(za - zb) / (2 * z0), ys * asqrt(4 * z02 * zb - (z02 - za + zb) * (z02 - za + zb)) / (2 * z0)];
				}
				forward.invert = function(x, y) {
					var y2 = y * y,
						cosza = Math.cos(Math.sqrt(y2 + (t = x + λa) * t)),
						coszb = Math.cos(Math.sqrt(y2 + (t = x + λb) * t)),
						t, d;
					return [Math.atan2(d = cosza - coszb, t = (cosza + coszb) * tanλ0), (y < 0 ? -1 : 1) * acos(Math.sqrt(t * t + d * d) * S)];
				};
				return forward;
			}

			function twoPointEquidistantProjection() {
				var points = [
						[0, 0],
						[0, 0]
					],
					m = projectionMutator(twoPointEquidistant),
					p = m(0),
					rotate = p.rotate;
				delete p.rotate;
				p.points = function(_) {
					if(!arguments.length) return points;
					points = _;
					var interpolate = d3.geo.interpolate(_[0], _[1]),
						origin = interpolate(.5),
						p = d3.geo.rotation([-origin[0], -origin[1]])(_[0]),
						b = interpolate.distance * .5,
						γ = -asin(Math.sin(p[1] * radians) / Math.sin(b));
					if(p[0] > 0) γ = π - γ;
					rotate.call(p, [-origin[0], -origin[1], -γ * degrees]);
					return m(b * 2);
				};
				return p;
			}
			(d3.geo.twoPointEquidistant = twoPointEquidistantProjection).raw = twoPointEquidistant;

			function twoPointAzimuthal(d) {
				var cosd = Math.cos(d);

				function forward(λ, φ) {
					var coordinates = d3.geo.gnomonic.raw(λ, φ);
					coordinates[0] *= cosd;
					return coordinates;
				}
				forward.invert = function(x, y) {
					return d3.geo.gnomonic.raw.invert(x / cosd, y);
				};
				return forward;
			}

			function twoPointAzimuthalProjection() {
				var points = [
						[0, 0],
						[0, 0]
					],
					m = projectionMutator(twoPointAzimuthal),
					p = m(0),
					rotate = p.rotate;
				delete p.rotate;
				p.points = function(_) {
					if(!arguments.length) return points;
					points = _;
					var interpolate = d3.geo.interpolate(_[0], _[1]),
						origin = interpolate(.5),
						p = d3.geo.rotation([-origin[0], -origin[1]])(_[0]),
						b = interpolate.distance * .5,
						γ = -asin(Math.sin(p[1] * radians) / Math.sin(b));
					if(p[0] > 0) γ = π - γ;
					rotate.call(p, [-origin[0], -origin[1], -γ * degrees]);
					return m(b);
				};
				return p;
			}
			(d3.geo.twoPointAzimuthal = twoPointAzimuthalProjection).raw = twoPointAzimuthal;

			function vanDerGrinten(λ, φ) {
				if(Math.abs(φ) < ε) return [λ, 0];
				var sinθ = Math.abs(φ / halfπ),
					θ = asin(sinθ);
				if(Math.abs(λ) < ε || Math.abs(Math.abs(φ) - halfπ) < ε) return [0, sgn(φ) * π * Math.tan(θ / 2)];
				var cosθ = Math.cos(θ),
					A = Math.abs(π / λ - λ / π) / 2,
					A2 = A * A,
					G = cosθ / (sinθ + cosθ - 1),
					P = G * (2 / sinθ - 1),
					P2 = P * P,
					P2_A2 = P2 + A2,
					G_P2 = G - P2,
					Q = A2 + G;
				return [sgn(λ) * π * (A * G_P2 + Math.sqrt(A2 * G_P2 * G_P2 - P2_A2 * (G * G - P2))) / P2_A2, sgn(φ) * π * (P * Q - A * Math.sqrt((A2 + 1) * P2_A2 - Q * Q)) / P2_A2];
			}
			vanDerGrinten.invert = function(x, y) {
				if(Math.abs(y) < ε) return [x, 0];
				if(Math.abs(x) < ε) return [0, halfπ * Math.sin(2 * Math.atan(y / π))];
				var x2 = (x /= π) * x,
					y2 = (y /= π) * y,
					x2_y2 = x2 + y2,
					z = x2_y2 * x2_y2,
					c1 = -Math.abs(y) * (1 + x2_y2),
					c2 = c1 - 2 * y2 + x2,
					c3 = -2 * c1 + 1 + 2 * y2 + z,
					d = y2 / c3 + (2 * c2 * c2 * c2 / (c3 * c3 * c3) - 9 * c1 * c2 / (c3 * c3)) / 27,
					a1 = (c1 - c2 * c2 / (3 * c3)) / c3,
					m1 = 2 * Math.sqrt(-a1 / 3),
					θ1 = acos(3 * d / (a1 * m1)) / 3;
				return [π * (x2_y2 - 1 + Math.sqrt(1 + 2 * (x2 - y2) + z)) / (2 * x), sgn(y) * π * (-m1 * Math.cos(θ1 + π / 3) - c2 / (3 * c3))];
			};
			(d3.geo.vanDerGrinten = function() {
				return projection(vanDerGrinten);
			}).raw = vanDerGrinten;

			function vanDerGrinten2(λ, φ) {
				if(Math.abs(φ) < ε) return [λ, 0];
				var sinθ = Math.abs(φ / halfπ),
					θ = asin(sinθ);
				if(Math.abs(λ) < ε || Math.abs(Math.abs(φ) - halfπ) < ε) return [0, sgn(φ) * π * Math.tan(θ / 2)];
				var cosθ = Math.cos(θ),
					A = Math.abs(π / λ - λ / π) / 2,
					A2 = A * A,
					x1 = cosθ * (Math.sqrt(1 + A2) - A * cosθ) / (1 + A2 * sinθ * sinθ);
				return [sgn(λ) * π * x1, sgn(φ) * π * asqrt(1 - x1 * (2 * A + x1))];
			}
			vanDerGrinten2.invert = function(x, y) {
				if(!x) return [0, halfπ * Math.sin(2 * Math.atan(y / π))];
				var x1 = Math.abs(x / π),
					A = (1 - x1 * x1 - (y /= π) * y) / (2 * x1),
					A2 = A * A,
					B = Math.sqrt(A2 + 1);
				return [sgn(x) * π * (B - A), sgn(y) * halfπ * Math.sin(2 * Math.atan2(Math.sqrt((1 - 2 * A * x1) * (A + B) - x1), Math.sqrt(B + A + x1)))];
			};
			(d3.geo.vanDerGrinten2 = function() {
				return projection(vanDerGrinten2);
			}).raw = vanDerGrinten2;

			function vanDerGrinten3(λ, φ) {
				if(Math.abs(φ) < ε) return [λ, 0];
				var sinθ = φ / halfπ,
					θ = asin(sinθ);
				if(Math.abs(λ) < ε || Math.abs(Math.abs(φ) - halfπ) < ε) return [0, π * Math.tan(θ / 2)];
				var A = (π / λ - λ / π) / 2,
					y1 = sinθ / (1 + Math.cos(θ));
				return [π * (sgn(λ) * asqrt(A * A + 1 - y1 * y1) - A), π * y1];
			}
			vanDerGrinten3.invert = function(x, y) {
				if(!y) return [x, 0];
				var y1 = y / π,
					A = (π * π * (1 - y1 * y1) - x * x) / (2 * π * x);
				return [x ? π * (sgn(x) * Math.sqrt(A * A + 1) - A) : 0, halfπ * Math.sin(2 * Math.atan(y1))];
			};
			(d3.geo.vanDerGrinten3 = function() {
				return projection(vanDerGrinten3);
			}).raw = vanDerGrinten3;

			function vanDerGrinten4(λ, φ) {
				if(!φ) return [λ, 0];
				var φ0 = Math.abs(φ);
				if(!λ || φ0 === halfπ) return [0, φ];
				var B = φ0 / halfπ,
					B2 = B * B,
					C = (8 * B - B2 * (B2 + 2) - 5) / (2 * B2 * (B - 1)),
					C2 = C * C,
					BC = B * C,
					B_C2 = B2 + C2 + 2 * BC,
					B_3C = B + 3 * C,
					λ0 = λ / halfπ,
					λ1 = λ0 + 1 / λ0,
					D = sgn(Math.abs(λ) - halfπ) * Math.sqrt(λ1 * λ1 - 4),
					D2 = D * D,
					F = B_C2 * (B2 + C2 * D2 - 1) + (1 - B2) * (B2 * (B_3C * B_3C + 4 * C2) + 12 * BC * C2 + 4 * C2 * C2),
					x1 = (D * (B_C2 + C2 - 1) + 2 * asqrt(F)) / (4 * B_C2 + D2);
				return [sgn(λ) * halfπ * x1, sgn(φ) * halfπ * asqrt(1 + D * Math.abs(x1) - x1 * x1)];
			}
			vanDerGrinten4.invert = function(x, y) {
				if(!x || !y) return [x, y];
				y /= π;
				var x1 = sgn(x) * x / halfπ,
					D = (x1 * x1 - 1 + 4 * y * y) / Math.abs(x1),
					D2 = D * D,
					B = 2 * y,
					i = 50;
				do {
					var B2 = B * B,
						C = (8 * B - B2 * (B2 + 2) - 5) / (2 * B2 * (B - 1)),
						C_ = (3 * B - B2 * B - 10) / (2 * B2 * B),
						C2 = C * C,
						BC = B * C,
						B_C = B + C,
						B_C2 = B_C * B_C,
						B_3C = B + 3 * C,
						F = B_C2 * (B2 + C2 * D2 - 1) + (1 - B2) * (B2 * (B_3C * B_3C + 4 * C2) + C2 * (12 * BC + 4 * C2)),
						F_ = -2 * B_C * (4 * BC * C2 + (1 - 4 * B2 + 3 * B2 * B2) * (1 + C_) + C2 * (-6 + 14 * B2 - D2 + (-8 + 8 * B2 - 2 * D2) * C_) + BC * (-8 + 12 * B2 + (-10 + 10 * B2 - D2) * C_)),
						sqrtF = Math.sqrt(F),
						f = D * (B_C2 + C2 - 1) + 2 * sqrtF - x1 * (4 * B_C2 + D2),
						f_ = D * (2 * C * C_ + 2 * B_C * (1 + C_)) + F_ / sqrtF - 8 * B_C * (D * (-1 + C2 + B_C2) + 2 * sqrtF) * (1 + C_) / (D2 + 4 * B_C2);
					B -= δ = f / f_;
				} while (δ > ε && --i > 0);
				return [sgn(x) * (Math.sqrt(D * D + 4) + D) * π / 4, halfπ * B];
			};
			(d3.geo.vanDerGrinten4 = function() {
				return projection(vanDerGrinten4);
			}).raw = vanDerGrinten4;
			var wagner4 = function() {
				var A = 4 * π + 3 * Math.sqrt(3),
					B = 2 * Math.sqrt(2 * π * Math.sqrt(3) / A);
				return mollweideBromley(B * Math.sqrt(3) / π, B, A / 6);
			}();
			(d3.geo.wagner4 = function() {
				return projection(wagner4);
			}).raw = wagner4;

			function wagner6(λ, φ) {
				return [λ * Math.sqrt(1 - 3 * φ * φ / (π * π)), φ];
			}
			wagner6.invert = function(x, y) {
				return [x / Math.sqrt(1 - 3 * y * y / (π * π)), y];
			};
			(d3.geo.wagner6 = function() {
				return projection(wagner6);
			}).raw = wagner6;

			function wagner7(λ, φ) {
				var s = .90631 * Math.sin(φ),
					c0 = Math.sqrt(1 - s * s),
					c1 = Math.sqrt(2 / (1 + c0 * Math.cos(λ /= 3)));
				return [2.66723 * c0 * c1 * Math.sin(λ), 1.24104 * s * c1];
			}
			wagner7.invert = function(x, y) {
				var t1 = x / 2.66723,
					t2 = y / 1.24104,
					p = Math.sqrt(t1 * t1 + t2 * t2),
					c = 2 * asin(p / 2);
				return [3 * Math.atan2(x * Math.tan(c), 2.66723 * p), p && asin(y * Math.sin(c) / (1.24104 * .90631 * p))];
			};
			(d3.geo.wagner7 = function() {
				return projection(wagner7);
			}).raw = wagner7;

			function wiechel(λ, φ) {
				var cosφ = Math.cos(φ),
					sinφ = Math.cos(λ) * cosφ,
					sin1_φ = 1 - sinφ,
					cosλ = Math.cos(λ = Math.atan2(Math.sin(λ) * cosφ, -Math.sin(φ))),
					sinλ = Math.sin(λ);
				cosφ = asqrt(1 - sinφ * sinφ);
				return [sinλ * cosφ - cosλ * sin1_φ, -cosλ * cosφ - sinλ * sin1_φ];
			}
			wiechel.invert = function(x, y) {
				var w = -.5 * (x * x + y * y),
					k = Math.sqrt(-w * (2 + w)),
					b = y * w + x * k,
					a = x * w - y * k,
					D = Math.sqrt(a * a + b * b);
				return [Math.atan2(k * b, D * (1 + w)), D ? -asin(k * a / D) : 0];
			};
			(d3.geo.wiechel = function() {
				return projection(wiechel);
			}).raw = wiechel;

			function winkel3(λ, φ) {
				var coordinates = aitoff(λ, φ);
				return [(coordinates[0] + λ / halfπ) / 2, (coordinates[1] + φ) / 2];
			}
			winkel3.invert = function(x, y) {
				var λ = x,
					φ = y,
					i = 25;
				do {
					var cosφ = Math.cos(φ),
						sinφ = Math.sin(φ),
						sin_2φ = Math.sin(2 * φ),
						sin2φ = sinφ * sinφ,
						cos2φ = cosφ * cosφ,
						sinλ = Math.sin(λ),
						cosλ_2 = Math.cos(λ / 2),
						sinλ_2 = Math.sin(λ / 2),
						sin2λ_2 = sinλ_2 * sinλ_2,
						C = 1 - cos2φ * cosλ_2 * cosλ_2,
						E = C ? acos(cosφ * cosλ_2) * Math.sqrt(F = 1 / C) : F = 0,
						F, fx = .5 * (2 * E * cosφ * sinλ_2 + λ / halfπ) - x,
						fy = .5 * (E * sinφ + φ) - y,
						δxδλ = .5 * F * (cos2φ * sin2λ_2 + E * cosφ * cosλ_2 * sin2φ) + .5 / halfπ,
						δxδφ = F * (sinλ * sin_2φ / 4 - E * sinφ * sinλ_2),
						δyδλ = .125 * F * (sin_2φ * sinλ_2 - E * sinφ * cos2φ * sinλ),
						δyδφ = .5 * F * (sin2φ * cosλ_2 + E * sin2λ_2 * cosφ) + .5,
						denominator = δxδφ * δyδλ - δyδφ * δxδλ,
						δλ = (fy * δxδφ - fx * δyδφ) / denominator,
						δφ = (fx * δyδλ - fy * δxδλ) / denominator;
					λ -= δλ, φ -= δφ;
				} while ((Math.abs(δλ) > ε || Math.abs(δφ) > ε) && --i > 0);
				return [λ, φ];
			};
			(d3.geo.winkel3 = function() {
				return projection(winkel3);
			}).raw = winkel3;
		};

	}, {}],
	28: [function(require, module, exports) {
		/**
		 * decoder - methods for decoding weather data
		 *
		 * Copyright (c) 2016 Cameron Beccario
		 */
		! function() {
			"use strict";

			var decoder = module.exports = {};

			/**
			 * Decodes a UTF8 string from an array of bytes.
			 *
			 * @param {Uint8Array} bytes an array of bytes
			 * @returns {String} the decoded String
			 */
			decoder.decodeUTF8 = function(bytes) {
				var charCodes = [];
				for(var i = 0; i < bytes.length;) {
					var b = bytes[i++];
					switch(b >> 4) {
						case 0xc:
						case 0xd:
							b = (b & 0x1f) << 6 | bytes[i++] & 0x3f;
							break;
						case 0xe:
							b = (b & 0x0f) << 12 | (bytes[i++] & 0x3f) << 6 | bytes[i++] & 0x3f;
							break;
						default:
							// use value as-is
					}
					charCodes.push(b);
				}
				return String.fromCharCode.apply(null, charCodes);
			};

			/*
			    function blockView(data, cols, rows) {
			        var area = cols * rows;
			        return {
			            x: function(i) {
			                return i % cols;
			            },
			            y: function(i) {
			                return Math.floor(i / cols) % rows;
			            },
			            z: function(i) {
			                return Math.floor(i / area);
			            },
			            valueAt: function(x, y, z) {
			                if (0 <= x && x < cols) {
			                    if (0 <= y && y < rows) {
			                        var i = z * area + y * cols + x;
			                        if (0 <= z && i < data.length) {
			                            return data[i];
			                        }
			                    }
			                }
			                return Number.NaN;
			            }
			        };
			    }
			*/

			decoder.varpackDecode = function(bytes, size) {
				var values = new Float32Array(size),
					i = 0,
					j = 0;
				while(i < bytes.length) {
					var b = bytes[i++];
					if(b < 128) {
						b = b << 25 >> 25;
					} else {
						switch(b >> 4) {
							case 0x8:
							case 0x9:
							case 0xa:
							case 0xb:
								b = (b << 26 >> 18) | bytes[i++];
								break;
							case 0xc:
							case 0xd:
								b = (b << 27 >> 11) | bytes[i++] << 8 | bytes[i++];
								break;
							case 0xe:
								b = (b << 28 >> 4) | bytes[i++] << 16 | bytes[i++] << 8 | bytes[i++];
								break;
							case 0xf:
								if(b === 255) {
									for(var run = 1 + bytes[i++]; run > 0; run--) {
										values[j++] = Number.NaN;
									}
									continue;
								} else {
									b = bytes[i++] << 24 | bytes[i++] << 16 | bytes[i++] << 8 | bytes[i++];
								}
								break;
						}
					}
					values[j++] = b;
				}
				return values;
			};

			decoder.undeltaPlane = function(values, cols, rows, grids) {
				var x, y, z, i, j, k, p;

				for(z = 0; z < grids; z++) {
					k = z * cols * rows;
					for(x = 1; x < cols; x++) {
						i = k + x;
						p = values[i - 1];
						values[i] += (p === p ? p : 0);
					}
					for(y = 1; y < rows; y++) {
						j = k + y * cols;
						p = values[j - cols];
						values[j] += (p === p ? p : 0);
						for(x = 1; x < cols; x++) {
							i = j + x;
							var a = values[i - 1];
							var b = values[i - cols];
							var c = values[i - cols - 1];
							p = a + b - c;
							values[i] += (p === p ? p : a === a ? a : b === b ? b : c === c ? c : 0);
						}
					}
				}

				return values;
			};

			decoder.dequantize = function(values, scaleFactor) {
				var m = Math.pow(10, scaleFactor);
				for(var i = 0; i < values.length; i++) {
					values[i] /= m;
				}
				return values;
			};

			/**
			 * Decodes a quantized delta-plane varpack array of floats.
			 *
			 * @param {Uint8Array} bytes the encoded values as an array of bytes
			 * @param cols size of the x dimension
			 * @param rows size of the y dimension
			 * @param grids size of the z dimension
			 * @param scaleFactor number of decimal digits after (+) or before (-) the decimal point to retain
			 * @returns {Float32Array} the decoded values
			 */
			decoder.decodePpak = function(bytes, cols, rows, grids, scaleFactor) {
				var values;
				values = decoder.varpackDecode(bytes, cols * rows * grids);
				values = decoder.undeltaPlane(values, cols, rows, grids);
				values = decoder.dequantize(values, scaleFactor);
				return values;
			};

			/**
			 * Decodes a ppak block from a buffer having the format:
			 * <pre>
			 *       int32   int32   int32      float32     byte[]
			 *     [ cols ][ rows ][ grids ][ scaleFactor ][ data ]
			 *      ----------------------------------------------
			 *                        length
			 * </pre>
			 * All multi-byte values are BE. The number of resulting values is cols * rows * grids.
			 *
			 * @param {ArrayBuffer} buffer the buffer
			 * @param offset buffer byte offset
			 * @param length the byte length of the block
			 * @returns {Float32Array} the decoded values
			 */
			decoder.decodePpakBlock = function(buffer, offset, length) {
				var view = new DataView(buffer, offset, length);
				return decoder.decodePpak(
					new Uint8Array(buffer, offset + 16, length - 16),
					view.getInt32(0), // cols
					view.getInt32(4), // rows
					view.getInt32(8), // grids
					view.getFloat32(12)); // scaleFactor
			};

			/**
			 * Earth-Pack (EPAK) format:
			 * <pre>
			 *     head  := "head" (BE alpha-4) length (BE int) json (UTF-8 JSON string)
			 *     block :=  type  (BE alpha-4) length (BE int) data (byte[])
			 *     tail  := "tail"
			 *     file  :=  head [block]* tail
			 *
			 *     head                                  block                           tail
			 *     ------------------------------------  ------------------------------  ------
			 *    ["head"][0x00000003][0x10, 0x11, 0x12]["ppak"][0x00000002][0xff, 0xff]["tail"]
			 *             ----------  ----------------  ------  ----------  ----------
			 *               length          json         type     length       data
			 * </pre>
			 *
			 * @param {ArrayBuffer} buffer the buffer to decode
			 * @param {Object} [options] decoding options: {headerOnly: boolean}
			 * @returns {{header: *, blocks: Array}} the decoded values
			 */
			decoder.decodeEpak = function(buffer, options) {
				var headerOnly = !!(options || {}).headerOnly;
				var i = 0;
				var view = new DataView(buffer);

				var head = decoder.decodeUTF8(new Uint8Array(buffer, i, 4));
				i += 4;
				if(head !== "head") {
					throw new Error("expected 'head' but found '" + head + "'");
				}

				var length = view.getInt32(i);
				i += 4;
				var header = JSON.parse(decoder.decodeUTF8(new Uint8Array(buffer, i, length)));
				i += length;

				var block;
				var blocks = [];
				var type;
				while((type = decoder.decodeUTF8(new Uint8Array(buffer, i, 4))) !== "tail" && !headerOnly) {
					i += 4;
					length = view.getInt32(i);
					i += 4;
					switch(type) {
						case "ppak":
							block = decoder.decodePpakBlock(buffer, i, length);
							break;
						default:
							throw new Error("unknown block type: " + type);
					}
					blocks.push(block);
					i += length;
				}

				return {
					header: header,
					blocks: blocks
				};
			};

		}();

	}, {}],
	29: [function(require, module, exports) {
		"use strict";

		/**
		 * earth - a project to visualize global weather data.
		 *
		 * Copyright (c) 2016 Cameron Beccario
		 *
		 * For a free version of this project, see https://github.com/cambecc/earth
		 */
		module.exports = function earth(app) {

			var d3 = require("d3");
			var topojson = require("topojson");
			var when = require("when");
			var _ = require("underscore");
			var Backbone = require("backbone");
			var µ = require("./micro");
			var utc = require("./utc");
			var globes = require("./globes");
			var products = require("./products")(app);
			var keyboard = require("./keyboard");

			var log = app.log;
			var bridge = app.bridge;

			log.debug("start...");

			var π = Math.PI;
			var MAX_TASK_TIME = 150; // amount of time before a task yields control (millis)
			var MIN_SLEEP_TIME = 10; // amount of time a task waits before resuming (millis)
			var MIN_MOVE = 6; // slack before a drag operation beings (pixels)
			var MOVE_END_WAIT = 750; // time to wait for a move operation to be considered done (millis)

			var INTENSITY_SCALE_STEP = 10; // step size of particle intensity color scale
			var PARTICLE_LINE_WIDTH = µ.isFF() ? 1.4 : 1; // line width of a drawn particle
			var PARTICLE_MULTIPLIER = 7; // particle count scalar (completely arbitrary--this values looks nice)
			var PARTICLE_REDUCTION = 0.75; // reduce particle count to this much of normal for mobile devices
			var FRAME_RATE = 40; // desired milliseconds per frame

			var REMAINING = "▫▫▫▫▫▫▫▫▫▫▫▫▫▫▫▫▫▫▫▫"; // glyphs for remaining progress bar
			var COMPLETED = "▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪"; // glyphs for completed progress bar

			var NULL_WIND_VECTOR = [NaN, NaN, NaN]; // undefined location outside the vector field [u, v, m]
			var HOLE_VECTOR = [NaN, NaN, 0]; // signifies a hole in the vector field
			var TRANSPARENT_BLACK = [0, 0, 0, 0]; // singleton 0 rgba

			var view = µ.view();

			/**
			 * An object to display various types of messages to the user.
			 */
			var report = function() {
				// Route all messages to bridge if supported.
				var s = d3.select("#status"),
					p = d3.select("#progress"),
					total = REMAINING.length;
				var showMessage = µ.bindup(bridge, "showMessage") || function(msg /*, isError*/ ) {
					s.text(msg);
				};
				var showProgress = µ.bindup(bridge, "showProgress") || function(amount) { // amount must be int
					if(0 <= amount && amount < 100) {
						var i = Math.ceil(amount / 100 * total);
						var bar = COMPLETED.substr(0, i) + REMAINING.substr(0, total - i);
						return p.classed("invisible", false).text(bar);
					}
					return p.classed("invisible", true).text(""); // progress complete
				};

				return {
					status: function(msg) {
						return s.classed("bad") ? s : showMessage(msg, false); // errors are sticky until reset
					},
					error: function(err, context) {
						var msg = err.status ? err.status + " " + err.message : err.message ? err.message : err;
						switch(err.status) {
							case -1:
								msg = "Server Down";
								break;
							case 404:
								msg = "No Data";
								break;
						}
						if(context) log.error("context", context + "");
						log.error("error", err);
						if(err.stack) log.error("stack", err.stack);
						s.classed("bad", true);
						showMessage(msg, true);
					},
					reset: function() {
						s.classed("bad", false);
						showMessage("", false);
					},
					progress: showProgress
				};
			}();

			function newAgent(name) {
				return µ.newAgent(name).on({
					"reject": report.error,
					"fail": report.error
				});
			}

			// Construct the page's main internal components:

			var configuration =
				µ.buildConfiguration(globes, products.overlayTypes()); // holds the page's current configuration settings
			var inputController = buildInputController(); // interprets drag/zoom operations
			var meshAgent = newAgent("mesh"); // map data for the earth
			var globeAgent = newAgent("globe"); // the model of the globe
			var gridAgent = newAgent("grid"); // the grid of weather data
			var rendererAgent = newAgent("renderer"); // the globe SVG renderer
			var argoAgent = newAgent("argo"); // SVG argo maker
			var fieldAgent = newAgent("field"); // the interpolated wind vector field
			var animatorAgent = newAgent("animator"); // the wind animator
			var overlayAgent = newAgent("overlay"); // color overlay over the animation
			var overlayGridAgent = newAgent("overlayGrid"); // grid points drawn on the overlay
			var buttonStateNotifier = newAgent("button"); // notifies consumers of button state changes

			/**
			 * The input controller is an object that translates move operations (drag and/or zoom) into mutations of the
			 * current globe's projection, and emits events so other page components can react to these move operations.
			 *
			 * D3's built-in Zoom behavior is used to bind to the document's drag/zoom events, and the input controller
			 * interprets D3's events as move operations on the globe. This method is complicated due to the complex
			 * event behavior that occurs during drag and zoom.
			 *
			 * D3 move operations usually occur as "zoomstart" -> ("zoom")* -> "zoomend" event chain. During "zoom" events
			 * the scale and mouse may change, implying a zoom or drag operation accordingly. These operations are quite
			 * noisy. What should otherwise be one smooth continuous zoom is usually comprised of several "zoomstart" ->
			 * "zoom" -> "zoomend" event chains. A debouncer is used to eliminate the noise by waiting a short period of
			 * time to ensure the user has finished the move operation.
			 *
			 * The "zoom" events may not occur; a simple click operation occurs as: "zoomstart" -> "zoomend". There is
			 * additional logic for other corner cases, such as spurious drags which move the globe just a few pixels
			 * (most likely unintentional), and the tendency for some touch devices to issue events out of order:
			 * "zoom" -> "zoomstart" -> "zoomend".
			 *
			 * This object emits clean "moveStart" -> ("move")* -> "moveEnd" events for move operations, and "click" events
			 * for normal clicks. Spurious moves emit no events.
			 */
			function buildInputController() {
				var globe, op = null,
					prevClick = {
						time: 0,
						mouse: [0, 0]
					};

				/**
				 * @returns {Object} an object to represent the state for one move operation.
				 */
				function newOp(startMouse, startScale) {
					return {
						type: "click", // initially assumed to be a click operation
						startMouse: startMouse,
						startScale: startScale,
						manipulator: globe.manipulator(startMouse, startScale)
					};
				}

				function start() {
					op = op || newOp(d3.mouse(this), globe.projection.scale()); // a new operation begins
				}

				function step() {
					var currentMouse = d3.mouse(this),
						currentScale = µ.coalesce(d3.event.scale, globe.projection.scale());
					op = op || newOp(currentMouse, 1); // Fix bug on some browsers where zoomstart fires out of order.
					if(op.type === "click" || op.type === "spurious") {
						var distanceMoved = µ.distance(currentMouse, op.startMouse);
						if(currentScale === op.startScale && distanceMoved < MIN_MOVE) {
							// to reduce annoyance, ignore op if mouse has barely moved and no zoom is occurring
							op.type = distanceMoved > 0 ? "click" : "spurious";
							return;
						}
						dispatch.trigger("moveStart");
						op.type = "drag";
					}
					if(currentScale != op.startScale || _.isNaN(currentMouse[0])) {
						// Whenever a scale change is detected, or mouse is undefined (such as during double-click),
						// stickily switch to a zoom operation.
						op.type = "zoom";
					}

					// when zooming, ignore whatever the mouse is doing--really cleans up behavior on touch devices
					op.manipulator.move(op.type === "zoom" ? null : currentMouse, currentScale);
					dispatch.trigger("move");
				}

				function end() {
					if(op === null) return;
					op.manipulator.end();
					if(op.type === "click") {
						// Ignore clicks that occur soon after the previous click and in the same location. Reduces noise
						// on touch devices where taps trigger both touchstart and mousedown events (i.e., two clicks).
						if(Date.now() - prevClick.time > 500 || µ.distance(prevClick.mouse, op.startMouse) >= MIN_MOVE) {
							dispatch.trigger("click", op.startMouse, globe.projection.invert(op.startMouse) || []);
							prevClick = {
								time: Date.now(),
								mouse: op.startMouse
							};
						}
					} else if(op.type !== "spurious") {
						signalEnd();
					}
					op = null; // the drag/zoom/click operation is over
				}

				var signalEnd = _.debounce(function() {
					if(!op || op.type !== "drag" && op.type !== "zoom") {
						configuration.save({
							orientation: globe.orientation()
						}, {
							source: "moveEnd"
						});
						dispatch.trigger("moveEnd");
					}
				}, MOVE_END_WAIT); // wait for a bit to decide if user has stopped moving the globe

				var zoom = d3.behavior.zoom()
					.on("zoomstart", start)
					.on("zoom", step)
					.on("zoomend", end);

				var drag = d3.behavior.drag()
					.on("dragstart", start)
					.on("drag", step)
					.on("dragend", end);

				d3.select("#display").call(µ.isEmbeddedInIFrame() && !µ.isKioskMode() ? drag : zoom);

				d3.select("#show-location").on("click", function() {
					if(navigator.geolocation) {
						report.status("Finding current position...");
						navigator.geolocation.getCurrentPosition(function(pos) {
							report.status("");
							locate(pos.coords.longitude, pos.coords.latitude);
						}, report.error);
					}
				});

				function reorient() {
					var options = arguments[2] || {};
					if(!globe || options.source === "moveEnd") {
						// reorientation occurred because the user just finished a move operation, so globe is already
						// oriented correctly.
						return;
					}
					dispatch.trigger("moveStart");
					globe.orientation(configuration.get("orientation"), view);
					zoom.scale(globe.projection.scale());
					dispatch.trigger("moveEnd");
				}

				var dispatch = _.extend({
					globe: function(_) {
						if(_) {
							globe = _;
							zoom.scaleExtent(globe.scaleExtent());
							reorient();
						}
						return _ ? this : globe;
					},
					cancelMove: function() {
						// Forcefully end the current move operation, if any.
						end();
					}
				}, Backbone.Events);
				return dispatch.listenTo(configuration, "change:orientation", reorient);
			}

			function orient(coord) {
				var globe = globeAgent.value();
				if(globe) {
					var rotate = globe.locate(coord);
					if(rotate) {
						globe.projection.rotate(rotate);
						configuration.save({
							orientation: globe.orientation()
						}); // triggers reorientation
					}
				}
			}

			function locate(lon, lat) {
				var λ = +lon,
					φ = +lat,
					coord = [λ, φ];
				if(λ !== λ || φ !== φ) {
					removeLocation();
					return;
				}
				saveLocation(null, coord);
				orient(coord);
			}

			/**
			 * @param resource the GeoJSON resource's URL
			 * @returns {Object} a promise for GeoJSON topology features: {boundaryLo:, boundaryHi:}
			 */
			function buildMesh(resource) {
				var cancel = this.cancel;
				report.status("Downloading...");
				var files = [];
				files.push(µ.loadJson(resource));
				if(µ.siteInstance() === "tara") {
					files.push(µ.loadJson(products.gaia("/data/tara/tara-plan.json")));
					files.push(µ.loadJson(products.gaia("/data/tara/tara-track.json")));
				}
				//files.push(µ.loadJson("/data/enel.json"));

				return when.all(files).spread(function(topo, taraPlan, taraTrack, enel) {
					if(cancel.requested) return null;
					log.time("building meshes");
					var o = topo.objects;
					var coastLo = topojson.feature(topo, µ.isMobile() ? o.coastline_tiny : o.coastline_110m);
					var coastHi = topojson.feature(topo, µ.isMobile() ? o.coastline_110m : o.coastline_50m);
					var lakesLo = topojson.feature(topo, µ.isMobile() ? o.lakes_tiny : o.lakes_110m);
					var lakesHi = topojson.feature(topo, µ.isMobile() ? o.lakes_110m : o.lakes_50m);
					var riversLo = topojson.feature(topo, µ.isMobile() ? o.rivers_tiny : o.rivers_110m);
					var riversHi = topojson.feature(topo, µ.isMobile() ? o.rivers_110m : o.rivers_50m);

					log.timeEnd("building meshes");
					return {
						coastLo: coastLo,
						coastHi: coastHi,
						lakesLo: lakesLo,
						lakesHi: lakesHi,
						riversLo: riversLo,
						riversHi: riversHi,
						tara: {
							plan: taraPlan,
							track: taraTrack
						},
						enel: enel,
					};
				});
			}

			/**
			 * @param {String} projectionName the desired projection's name.
			 * @returns {Object} a promise for a globe object.
			 */
			function buildGlobe(projectionName) {
				var builder = globes[projectionName];
				if(!builder) {
					return when.reject("Unknown projection: " + projectionName);
				}
				return when(builder(view));
			}

			// Some hacky stuff to ensure only one download can be in progress at a time.
			var downloadsInProgress = 0;

			function buildGrids(isNewPrimaryGrid) {
				report.status("Downloading...");
				log.time("build grids");
				var cancel = this.cancel;
				downloadsInProgress++;
				return when.all(products.productsFor(configuration.attributes)).then(function(products) {

					var loaded = when.map(products, function(product) {
						return product.load(cancel);
					});
					return when.all(loaded).then(function(products) {
						return {
							primaryGrid: products[0],
							overlayGrid: products[1] || products[0],
							isNewPrimaryGrid: !!isNewPrimaryGrid
						};
					}).otherwise(function(err) {
						report.error(err);
						return {
							primaryGrid: products[0],
							overlayGrid: products[1] || products[0],
							isNewPrimaryGrid: !!isNewPrimaryGrid
						};
					});

				}).ensure(function() {
					downloadsInProgress--;
					log.timeEnd("build grids");
				});
			}

			/**
			 * Modifies the configuration to navigate to the chronologically next or previous data layer.
			 */
			function navigate(step) {
				if(downloadsInProgress > 0) {
					log.debug("Download in progress--ignoring nav request.");
					return;
				}
				var next = gridAgent.value().overlayGrid.navigate(step);
				if(next) {
					configuration.save(µ.dateToConfig(next));
				}
			}

			function constructTaraElements(svg, tara, orientation) {
				var g = svg.append("g");

				//g.append("path").attr("class", "tara-route tara-plan-border").datum(tara.plan);
				g.append("path").attr("class", "tara-route tara-plan").datum(tara.plan);

				var points = tara.track.geometries;
				points = points.filter(function(e, i) {
					return i > (points.length - 10) || i % 2 == 0;
				});

				var track = {
					type: "LineString",
					coordinates: _.pluck(points, "coordinates")
				};
				g.append("path").attr("class", "tara-route tara-track-border").datum(track);
				g.append("path").attr("class", "tara-route tara-track").datum(track);

				var taraLoc = {
					type: "Point",
					coordinates: _.last(track.coordinates),
					size: 4
				};
				if(orientation === "" || orientation.substr(0, 2) === ",,") {
					orient(taraLoc.coordinates);
				}
				g.append("path").attr("class", "tara-loc").datum(taraLoc);

				var last = _.last(points);
				var data = {
					date: last["date"],
					heading: +µ.coalesce(last["heading"], NaN),
					speed: +µ.coalesce(last["speed"], NaN) * 3.6,
					air_temp: +µ.coalesce(last["air_temp"], NaN),
					water_temp: +µ.coalesce(last["water_temp"], NaN),
					pressure: +µ.coalesce(last["pressure"], NaN),
				};
				var fields = {
					date: utc.print(utc.localParts(data.date), "{yyyy}-{MM}-{dd} {hh}:{mm}"),
					heading: isNaN(data.heading) ? "N/A" : data.heading.toFixed(0),
					speed: isNaN(data.speed) ? "N/A" : data.speed.toFixed(1),
					air_temp: isNaN(data.air_temp) ? "N/A" : data.air_temp.toFixed(1),
					water_temp: isNaN(data.water_temp) ? "N/A" : data.water_temp.toFixed(1),
					pressure: isNaN(data.pressure) ? "N/A" : data.pressure.toFixed(0),
				};
				var div = d3.select("#tara-stats");
				div.selectAll("*").remove();
				div.append("p").text("Tara stats:");
				div.append("p").classed("stat", true)
					.text(fields.heading + "° @ " + fields.speed + " km/h");
				div.append("p").classed("stat", true).text(fields.air_temp + " °C (air)");
				div.append("p").classed("stat", true).text(fields.water_temp + " °C (water)");
				div.append("p").classed("stat", true).text(fields.pressure + " hPa");
				div.append("p").text(fields.date);
				div.selectAll(".stat").attr("style", "padding-left: 1em");
				div.classed("invisible", false);
			}

			function constructEnelElements(svg, enel) {
				var g = svg.append("g");

				enel.features.forEach(function(e) {
					e.size = µ.clamp(Math.log(e.properties.capacity) * 2, 4, 8);
				});

				g.selectAll("path")
					.data(enel.features)
					.enter().append("path")
					.attr("class", function(d) {
						return d.properties.type + "-mark";
					});
			}

			function buildRenderer(mesh, globe) {
				if(!mesh || !globe) return null;

				report.status("Rendering Globe...");
				log.time("rendering map");

				// UNDONE: better way to do the following?
				var dispatch = _.clone(Backbone.Events);
				if(rendererAgent._previous) {
					rendererAgent._previous.stopListening();
				}
				rendererAgent._previous = dispatch;

				var mapSvg = d3.select("#map");
				var foregroundSvg = d3.select("#foreground");
				var orientation = configuration.get("orientation");

				// First clear map and foreground svg contents.
				µ.removeChildren(mapSvg.node());
				µ.removeChildren(foregroundSvg.node());
				// Create new map svg elements.
				globe.defineMap(mapSvg, foregroundSvg);
				globe.afterMove(mapSvg, foregroundSvg);
				globe.orientation(orientation, view); // This allows the path function to work.

				var path = d3.geo.path().projection(globe.projection).pointRadius(function(d) {
					return d.size || 7;
				});
				var coastline = d3.select(".coastline");
				var lakes = d3.select(".lakes");
				var rivers = d3.select(".rivers");

				if(µ.siteInstance() === "tara") {
					constructTaraElements(foregroundSvg, mesh.tara, orientation);
				}
				//constructEnelElements(foregroundSvg, mesh.enel);

				d3.selectAll("path").attr("d", path); // do an initial draw -- fixes issue with safari

				// Draw the location mark if one is currently visible.
				updateLocation();

				// Throttled draw method helps with slow devices that would get overwhelmed by too many redraw events.
				var REDRAW_WAIT = 5; // milliseconds
				var doDraw_throttled = _.throttle(doDraw, REDRAW_WAIT, {
					leading: false
				});

				function doDraw() {
					d3.selectAll("path").attr("d", path);
					rendererAgent.trigger("redraw");
					doDraw_throttled = _.throttle(doDraw, REDRAW_WAIT, {
						leading: false
					});
				}

				// Attach to map rendering events on input controller.
				dispatch.listenTo(
					inputController, {
						moveStart: function() {
							globe.beforeMove(mapSvg, foregroundSvg);
							coastline.datum(mesh.coastLo);
							lakes.datum(mesh.lakesLo);
							rivers.datum(mesh.riversLo);
							rendererAgent.trigger("start");
						},
						move: function() {
							doDraw_throttled();
						},
						moveEnd: function() {
							globe.afterMove(mapSvg, foregroundSvg);
							coastline.datum(mesh.coastHi);
							lakes.datum(mesh.lakesHi);
							rivers.datum(mesh.riversHi);
							when(true).then(function() {
								d3.selectAll("path").attr("d", path);
							}).otherwise(report.error);
							rendererAgent.trigger("render");
						},
					});

				// Finally, inject the globe model into the input controller. Do it on the next event turn to ensure
				// renderer is fully set up before events start flowing.
				when(true).then(function() {
					inputController.globe(globe);
				}).otherwise(report.error);

				log.timeEnd("rendering map");
				return "ready";
			}

			function addArgo() {
				var globe = globeAgent.value();
				if(!globe) return null;

				var svg = d3.select("#foreground");
				var type = configuration.get("argoFloat");
				if(svg.selectAll(".argo-" + type).size() > 0) {
					return null;
				}
				var cancel = this.cancel;
				var files = [µ.loadJson(products.argoUrl("argo.floats.json"))];
				return when.all(files).spread(function(argo) {
					if(cancel.requested) return null;

					log.time("building argo");

					var svg = d3.select("#foreground");
					svg.selectAll(".argo").remove();

					var validTypes = ["recent", "active", "planned", "dead"];
					if(_.indexOf(validTypes, type) >= 0) {
						var path = d3.geo.path().projection(globe.projection).pointRadius(3);
						var floats = _.pluck(argo.filter(function(e) {
							return e.status === type;
						}), "coord");
						svg.append("path")
							.classed("argo argo-" + type, true)
							.datum({
								type: "MultiPoint",
								coordinates: floats,
								size: 3
							})
							.attr("d", path);
					}

					log.timeEnd("building argo");
				});
			}

			function copyTypedArray(target, source) {
				// Some browsers do not support TypedArray.prototype.set, like Android.
				if(_.isFunction(target.set)) {
					target.set(source);
				} else {
					for(var i = 0; i < source.length; i++) {
						target[i] = source[i];
					}
				}
			}

			function createMask(globe) {
				if(!globe) return null;

				log.time("render mask");

				// Create a detached canvas, ask the model to define the mask polygon, then fill with an opaque color.
				var width = view.width,
					height = view.height;
				var canvas = d3.select(document.createElement("canvas")).attr("width", width).attr("height", height).node();
				var context = globe.defineMask(canvas.getContext("2d"));
				context.fillStyle = "rgba(255, 0, 0, 1)";
				context.fill();

				// d3.select("#display").node().appendChild(canvas);  // make mask visible for debugging

				// Grab a _copy_ of the ImageData. Works around a Chrome bug where holding onto the result of getImageData
				// sometimes results in a blank screen, presumably because the associated detached canvas is garbage collected.
				var imageData = context.createImageData(width, height),
					data = imageData.data; // layout: [r, g, b, a, ...]
				copyTypedArray(data, context.getImageData(0, 0, width, height).data);
				//var imageData = context.getImageData(0, 0, width, height), data = imageData.data;  // layout: [r, g, b, a, ...]

				log.timeEnd("render mask");
				return {
					imageData: imageData,
					isVisible: function(x, y) {
						var i = (y * width + x) * 4;
						return data[i + 3] > 0; // non-zero alpha means pixel is visible
					},
					set: function(x, y, rgba) {
						var i = (y * width + x) * 4;
						data[i] = rgba[0];
						data[i + 1] = rgba[1];
						data[i + 2] = rgba[2];
						data[i + 3] = rgba[3];
						return this;
					}
				};
			}

			function createField(rows, mask, bounds) {
				var xMin = bounds.x;
				var field = {};

				/**
				 * Copies the array [x, y, u, v, m] into Array 'a' starting at index i. If x or y is out of bounds,
				 * then sets the u value to NaN.
				 *
				 * @param x {Number}
				 * @param y {Number}
				 * @param a {Float32Array}
				 * @param i {Number}
				 */
				field.move = function(x, y, a, i) {
					var k = Math.round(y);
					if(0 <= k && k < rows.length) {
						var row = rows[k];
						var j = (Math.round(x) - xMin) * 3;
						if(row && 0 <= j && j < row.length) {
							a[i] = x;
							a[i + 1] = y;
							a[i + 2] = row[j];
							a[i + 3] = row[j + 1];
							a[i + 4] = row[j + 2];
							return;
						}
					}
					a[i] = x;
					a[i + 1] = y;
					a[i + 2] = NaN;
					a[i + 3] = NaN;
					a[i + 4] = NaN;
				};

				/**
				 * @returns {boolean} true if the field is valid at the point (x, y)
				 */
				field.isDefined = function(x, y) {
					var k = Math.round(y);
					if(0 <= k && k < rows.length) {
						var row = rows[k];
						var j = (Math.round(x) - xMin) * 3;
						if(row && 0 <= j && j < row.length) {
							return row[j] === row[j];
						}
					}
					return false;
				};

				/**
				 * @returns {boolean} true if the point (x, y) lies inside the outer boundary of the vector field, even if
				 *          the vector field has a hole (is undefined) at that point, such as at an island in a field of
				 *          ocean currents.
				 */
				field.isInsideBoundary = function(x, y) {
					var a = new Float32Array(5); // [x, y, u, v, m]
					field.move(x, y, a, 0);
					return a[4] === a[4]; // true if magnitude is defined or is HOLE_VECTOR
				};

				field.overlay = mask.imageData;

				return field;
			}

			/**
			 * Calculate distortion of the wind vector caused by the shape of the projection at point (x, y). The wind
			 * vector is modified in place and returned by this function.
			 */
			function distort(projection, λ, φ, x, y, scale, wind) {
				var u = wind[0] * scale;
				var v = wind[1] * scale;
				var d = µ.distortion(projection, λ, φ, x, y);

				// Scale distortion vectors by u and v, then add.
				wind[0] = d[0] * u + d[2] * v;
				wind[1] = d[1] * u + d[3] * v;
				return wind;
			}

			function interpolateField(globe, grids) {
				if(!globe || !grids || !rendererAgent.value()) return null;

				var mask = createMask(globe);
				var primaryGrid = grids.primaryGrid;
				var overlayGrid = grids.overlayGrid;

				// nothing to do if products failed to load and have no data
				if(!primaryGrid.field || !overlayGrid.field) return null;

				var interpolationType = "bilinear";
				var interpolate = primaryGrid.field()[interpolationType];
				var overlayInterpolate = overlayGrid.field()[interpolationType];

				log.time("interpolating field");
				var d = when.defer(),
					cancel = this.cancel;

				var projection = globe.optimizedProjection();
				var invert = projection.invert.bind(projection);

				var bounds = globe.bounds(view);
				// How fast particles move on the screen (arbitrary value chosen for aesthetics).
				var velocityScale = primaryGrid.particles.velocityScale;

				var rows = [];
				var point = [];
				var y = bounds.y;
				var hasDistinctOverlay = primaryGrid !== overlayGrid;
				var colorScale = overlayGrid.scale;

				var hd = configuration.get("hd"),
					step = hd ? 1 : 2;

				function interpolateRow(y) {
					var row = new Float32Array(bounds.width * 3); // [u0, v0, m0, u1, v1, m1, ...]
					for(var x = bounds.x, i = 0; x <= bounds.xMax; x += step, i += step * 3) {
						var wind = NULL_WIND_VECTOR;
						if(mask.isVisible(x, y)) {
							point[0] = x, point[1] = y;
							var coord = invert(point);
							var color = TRANSPARENT_BLACK;
							if(coord) {
								var λ = coord[0],
									φ = coord[1];
								if(λ === λ) {
									wind = interpolate(coord);
									var scalar = wind[2];
									if(scalar === scalar) {
										wind = distort(projection, λ, φ, x, y, velocityScale, wind);
										scalar = wind[2];
									} else {
										wind = HOLE_VECTOR;
									}
									if(hasDistinctOverlay) {
										scalar = µ.scalarize(overlayInterpolate(coord));
									}
									if(scalar === scalar) {
										color = colorScale.gradient(scalar, overlayGrid.alpha.animated);
									}
								}
							}
							mask.set(x, y, color);
							if(!hd) {
								mask.set(x + 1, y, color).set(x, y + 1, color).set(x + 1, y + 1, color);
							}
							//mask.set(x, y  , color).set(x+1, y  , color)/*.set(x+2, y  , color).set(x+3, y  , color)*/;
							//mask.set(x, y+1, color).set(x+1, y+1, color)/*.set(x+2, y+1, color).set(x+3, y+1, color)*/;
							//mask.set(x, y+2, color).set(x+1, y+2, color).set(x+2, y+2, color).set(x+3, y+2, color);
							//mask.set(x, y+3, color).set(x+1, y+3, color).set(x+2, y+3, color).set(x+3, y+3, color);
						}
						/*row[i+ 9] = row[i+6] =*/
						/*row[i+3] =*/
						row[i] = wind[0];
						/*row[i+10] = row[i+7] =*/
						/*row[i+4] =*/
						row[i + 1] = wind[1];
						/*row[i+11] = row[i+8] =*/
						/*row[i+5] =*/
						row[i + 2] = wind[2];
						if(!hd) {
							row[i + 3] = wind[0];
							row[i + 4] = wind[1];
							row[i + 5] = wind[2];
						}
					}
					rows[y] = row;
					if(!hd) {
						rows[y + 1] = row;
					}
					///*rows[y+3] = rows[y+2] =*/ /*rows[y+1] =*/ rows[y] = row;
				}

				report.status("");
				report.progress(0); // signal that we are starting interpolation

				(function batchInterpolate() {
					try {
						if(!cancel.requested) {
							var start = Date.now();
							while(y <= bounds.yMax) {
								interpolateRow(y);
								y += step;
								if((Date.now() - start) > MAX_TASK_TIME) {
									// Interpolation is taking too long. Schedule the next batch for later and yield.
									report.progress(Math.round((y - bounds.y) / (bounds.yMax - bounds.y) * 100));
									setTimeout(batchInterpolate, MIN_SLEEP_TIME);
									return;
								}
							}
						}
						d.resolve(createField(rows, mask, bounds));
					} catch(e) {
						d.reject(e);
					}
					report.progress(100); // 100% complete
					log.timeEnd("interpolating field");
				})();

				return d.promise;
			}

			function animate(globe, field, grids) {
				if(!globe || !field || !grids || !configuration.get("animate")) return;

				if(grids.isNewPrimaryGrid) {
					µ.clearCanvas(d3.select("#animation").node()); // clear animation artifacts
				}

				var cancel = this.cancel;
				var bounds = globe.bounds(view);
				// maxIntensity is the velocity at which particle color intensity is maximum
				var colorStyles = µ.windIntensityColorScale(INTENSITY_SCALE_STEP, grids.primaryGrid.particles.maxIntensity);
				var particleCount = Math.round(bounds.width * PARTICLE_MULTIPLIER);
				if(µ.isMobile()) {
					particleCount = Math.floor(particleCount * PARTICLE_REDUCTION);
				}

				log.debug("particle count: %s", particleCount);

				var particles = new Float32Array(particleCount * 5);
				var ages = new Int32Array(particleCount);
				var batches = colorStyles.map(function() {
					return new Float32Array(particleCount * 4);
				});
				var sizes = new Int32Array(batches.length);
				var xMin = bounds.x,
					yMin = bounds.y,
					width = bounds.width,
					height = bounds.height;

				function randomize(i) {
					var x = xMin + Math.random() * width;
					var y = yMin + Math.random() * height;
					field.move(x, y, particles, i);
				}

				function randomizeWell(i) { // This function is hrm, but avoids "pulsing"
					for(var attempts = 0; attempts < 10; attempts++) {
						randomize(i);
						if(particles[i + 2] === particles[i + 2]) return;
					}
				}

				var maxAge, evolve;
				var g = d3.select("#animation").node().getContext("2d");
				if(grids.primaryGrid.particles.waves) {
					maxAge = 40;
					evolve = evolveWaves;
					g.fillStyle = "rgba(0, 0, 0, 0.90)";
				} else {
					maxAge = 100;
					evolve = evolveParticles;
					g.fillStyle = µ.isFF() ? "rgba(0, 0, 0, 0.95)" : "rgba(0, 0, 0, 0.97)"; // FF Mac alpha behaves oddly
				}
				g.lineWidth = PARTICLE_LINE_WIDTH;

				for(var i = 0, j = 0; i < particleCount; i += 1, j += 5) {
					ages[i] = _.random(0, maxAge);
					randomizeWell(j);
				}

				var easeFactor = new Float32Array(maxAge);
				for(var k = 0; k < easeFactor.length; k++) {
					easeFactor[k] = (Math.sin(-π / 2 + k / 7) / 2 + 1 / 2); // fade in/out line intensity
				}

				var scale = globe.projection.scale();
				scale = 600 / scale * Math.pow(Math.log(scale) / Math.log(600), 2.5); // use shallower exponential speed scale

				function evolveWaves() {
					for(var s = 0; s < sizes.length; s++) {
						sizes[s] = 0;
					}
					for(var i = 0, j = 0; i < particleCount; i += 1, j += 5) {
						if(++ages[i] >= maxAge) {
							ages[i] = 0;
							randomize(j);
						}

						var x = particles[j];
						var y = particles[j + 1];
						var u = particles[j + 2];
						var v = particles[j + 3];
						var xt = x + u * scale;
						var yt = y + v * scale;
						var m = particles[j + 4];

						if(m !== m || !field.isDefined(xt, yt)) {
							ages[i] = maxAge; // particle has escaped the game grid
						} else {
							particles[j] = xt;
							particles[j + 1] = yt;

							// width of wave
							var mag = Math.sqrt(u * u + v * v) / 2.5; // CONSIDER: would be nice to retain unscaled m...
							var du = u / mag,
								dv = v / mag;

							// Path from (x,y) to (xt,yt) is visible, so add this particle to the appropriate draw bucket.
							var si = colorStyles.indexFor(m * easeFactor[ages[i]]);
							var sj = 4 * sizes[si]++;
							var batch = batches[si];
							batch[sj] = x - dv;
							batch[sj + 1] = y + du;
							batch[sj + 2] = x + dv;
							batch[sj + 3] = y - du;
						}
					}
				}

				function evolveParticles() {
					for(var s = 0; s < sizes.length; s++) {
						sizes[s] = 0;
					}
					for(var i = 0, j = 0; i < particleCount; i += 1, j += 5) {
						if(++ages[i] >= maxAge) {
							ages[i] = 0;
							randomize(j);
						}

						var x = particles[j]; // x
						var y = particles[j + 1]; // y
						var xt = x + particles[j + 2]; // u
						var yt = y + particles[j + 3]; // v
						var m = particles[j + 4]; // m

						if(xt === xt) {
							field.move(xt, yt, particles, j);
							var u = particles[j + 2];
							if(u === u) {
								// Path from (x,y) to (xt,yt) is visible, so add this particle to the appropriate draw bucket.
								var si = colorStyles.indexFor(m);
								var sj = 4 * sizes[si]++;
								var batch = batches[si];
								batch[sj] = x;
								batch[sj + 1] = y;
								batch[sj + 2] = xt;
								batch[sj + 3] = yt;
							} else {
								ages[i] = maxAge; // particle has escaped the game grid
							}
						} else {
							ages[i] = maxAge; // particle has escaped the game grid
						}
					}
				}

				function draw() {
					// Fade existing trails.
					g.globalCompositeOperation = "destination-in";
					g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
					g.globalCompositeOperation = "source-over";

					// Draw new trails.
					for(var i = 0; i < batches.length; i++) {
						var batch = batches[i];
						var size = 4 * sizes[i];
						if(size > 0) {
							g.beginPath();
							g.strokeStyle = colorStyles[i];
							for(var j = 0; j < size; j += 4) {
								g.moveTo(batch[j], batch[j + 1]);
								g.lineTo(batch[j + 2], batch[j + 3]);
							}
							g.stroke();
						}
					}
				}

				(function frame() {
					if(cancel.requested) {
						return;
					}
					evolve();
					draw();
					setTimeout(frame, FRAME_RATE);
				})();
			}

			function drawGridPoints(ctx, layer, globe) {
				if(!layer || !globe || !configuration.get("showGridPoints")) return;

				ctx.fillStyle = "rgba(255, 255, 255, 0.75)";
				var width = view.width,
					height = view.height;
				var stream = globe.projection.stream({
					point: function(x, y) {
						if(0 <= x && x < width && 0 <= y && y < height) {
							// fillRect is processed at the end of the event turn, but it alpha blends, as opposed to setting
							// the pixel directly.
							ctx.fillRect(Math.round(x), Math.round(y), 1, 1);
						}
					},
				});
				var cancel = this.cancel;
				var grid = layer.grid(),
					field = layer.field(),
					valueAt = field.valueAt;
				var i = 0;

				// Draw grid points in batches.
				(function work() {
					if(!cancel.requested) {
						var end = Date.now() + MAX_TASK_TIME / 4;
						i = grid.forEach(function(λ, φ, i) {
							var v = µ.scalarize(valueAt(i));
							if(v === v) {
								λ = µ.floorMod(180 + λ, 360) - 180; // some projections don't clamp to [-180, 180)
								stream.point(λ, φ);
							}
							return Date.now() > end;
						}, i);
						if(i === i) {
							setTimeout(work, MIN_SLEEP_TIME);
						}
					}
				})();
			}

			function brighten(data, alpha) {
				for(var i = 3; i < data.length; i += 4) {
					if(data[i] !== 0) {
						data[i] = alpha;
					}
				}
			}

			function drawOverlay(field, overlayType, animate) {
				if(!field || !rendererAgent.value()) return;

				var ctx = d3.select("#overlay").node().getContext("2d"),
					grid = (gridAgent.value() || {}).overlayGrid;

				µ.clearCanvas(d3.select("#overlay").node());
				µ.clearCanvas(d3.select("#scale").node());
				if(overlayType) {
					if(overlayType !== "off") {
						if(!animate) {
							// No animation, so brighten the overlay.
							brighten(field.overlay.data, grid.alpha.single);
						}
						ctx.putImageData(field.overlay, 0, 0);
					}
					overlayGridAgent.submit(drawGridPoints, ctx, grid, globeAgent.value());
				}

				if(grid) {
					// Draw color bar for reference.
					var colorBar = d3.select("#scale"),
						scale = grid.scale,
						bounds = scale.bounds;
					var c = colorBar.node(),
						g = c.getContext("2d"),
						n = c.width - 1;
					var spread = scale.spread || _.partial(µ.spread, _, bounds[0], bounds[1]);
					for(var i = 0; i <= n; i++) {
						var rgb = scale.gradient(spread(i / n), 1);
						g.fillStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
						g.fillRect(i, 0, 1, c.height);
					}

					// Show tooltip on hover.
					colorBar.on("mousemove", function() {
						var x = d3.mouse(this)[0];
						var pct = (Math.round(x) - 2) / (n - 2);
						var value = spread(µ.clamp(pct, 0, 1));
						var elementId = grid.type === "wind" ? "#location-wind-units" : "#location-value-units";
						var units = createUnitToggle(elementId, grid).value();
						colorBar.attr("title", µ.formatScalar(value, units) + " " + (units.tooltip || units.label));
					});
				}
			}

			/**
			 * Get the grid date.
			 */
			function validityDate(grids) {
				if(grids) {
					return grids.overlayGrid.date(); // Use grid date if we have it.
				}
				// Otherwise use date as described by the configuration.
				var date = configuration.get("date");
				return date === "current" ? null : _.clone(date);
			}

			/**
			 * Display the grid's validity date in the menu. Allow toggling between local and UTC time.
			 */
			function showDate(grids) {
				var date = validityDate(grids),
					isLocal = d3.select("#data-date").classed("local");
				var formatted = "";
				if(date) {
					formatted = utc.print((isLocal ? utc.localParts : utc.parts)(utc.date(date)), "{yyyy}-{MM}-{dd} {hh}:{mm}");
				}
				d3.select("#data-date").text(formatted + " " + (isLocal ? "Local" : "UTC"));
				d3.select("#toggle-zone").text("⇄ " + (isLocal ? "UTC" : "Local"));

				// var div = d3.select("#tara-stats");
				// div.classed("invisible", false).selectAll("*").remove();
				// div.append("p").text(utc.print(date, "{yyyy}-{MM}-{dd} {hh}:{mm}Z"));

				return date;
			}

			/**
			 * Display the grids' types in the menu.
			 */
			function showGridDetails(grids) {
				var date = showDate(grids);
				var descriptionHTML = "",
					sourceHTML = "";
				if(grids) {
					var langCode = µ.siteLangCode();
					var pd = grids.primaryGrid.descriptionHTML(langCode),
						od = grids.overlayGrid.descriptionHTML(langCode);
					descriptionHTML = od.name + od.qualifier;
					if(grids.primaryGrid !== grids.overlayGrid) {
						// Combine both grid descriptions together with a " + " if their qualifiers are the same.
						descriptionHTML = (pd.qualifier === od.qualifier ? pd.name : pd.name + pd.qualifier) + " + " + descriptionHTML;
					}
					sourceHTML = grids.overlayGrid.sourceHTML || "";
				}
				d3.select("#data-layer").html(descriptionHTML);
				d3.select("#data-center").html(sourceHTML);
				if(bridge.productChange) {
					bridge.productChange(date.getTime(), descriptionHTML, sourceHTML);
				}
			}

			/**
			 * Constructs a toggler for the specified product's units, storing the toggle state on the element having
			 * the specified id. For example, given a product having units ["m/s", "mph"], the object returned by this
			 * method sets the element's "data-index" attribute to 0 for m/s and 1 for mph. Calling value() returns the
			 * currently active units object. Calling next() increments the index.
			 */
			function createUnitToggle(id, product) {
				var units = product.units,
					size = units.length;
				var index = +(d3.select(id).attr("data-index") || 0) % size;
				return {
					value: function() {
						return units[index];
					},
					next: function() {
						d3.select(id).attr("data-index", index = ((index + 1) % size));
					}
				};
			}

			/**
			 * Display the specified wind value. Allow toggling between the different types of wind units.
			 */
			function showWindAtLocation(wind, product) {
				var unitToggle = createUnitToggle("#location-wind-units", product),
					units = unitToggle.value();
				d3.select("#location-wind").text(µ.formatVector(wind, units));
				d3.select("#location-wind-units").html(units.label).on("click", function() {
					unitToggle.next();
					showWindAtLocation(wind, product);
				});
			}

			/**
			 * Display the specified overlay value. Allow toggling between the different types of supported units.
			 */
			function showOverlayValueAtLocation(value, product) {
				var unitToggle = createUnitToggle("#location-value-units", product),
					units = unitToggle.value();
				d3.select("#location-value").text(µ.formatScalar(value, units));
				d3.select("#location-value-units").html(units.label).on("click", function() {
					unitToggle.next();
					showOverlayValueAtLocation(value, product);
				});
			}

			/**
			 * Display a local data callout at the coordinates specified by the hash fragment "loc=lon,lat". The location may
			 * not be valid, in which case no callout is displayed. Display location data for both the primary grid and overlay
			 * grid, performing interpolation when necessary.
			 */
			function updateLocation() {
				d3.selectAll(".location").text("");
				d3.select("#location-close").classed("invisible", true);

				var coord = configuration.get("loc") || [],
					λ = +coord[0],
					φ = +coord[1];
				if(λ !== λ || φ !== φ) {
					d3.select(".location-mark").remove();
					if(bridge.showLocationDetails) {
						bridge.showLocationDetails("", "{}");
					}
					return;
				}

				var grids = gridAgent.value(),
					field = fieldAgent.value(),
					globe = globeAgent.value();
				if(!grids || !field || !globe) return;

				var primary = grids.primaryGrid,
					overlay = grids.overlayGrid,
					coordString, units = {};

				// Show coordinates.
				d3.select("#location-coord").text(coordString = µ.formatCoordinates(λ, φ));
				d3.select("#location-close").classed("invisible", false);

				// Show primary grid value.
				var vector = primary.interpolate([λ, φ]);
				if(vector[0] === vector[0]) {
					showWindAtLocation(vector, primary);
					units[primary.type] = _.object(primary.units.map(function(unit) {
						return [unit.label, µ.formatVector(vector, unit)];
					}));
				}

				// Show overlay grid value.
				var scalar = overlay !== primary ? µ.scalarize(overlay.interpolate([λ, φ])) : NaN;
				if(scalar === scalar) {
					showOverlayValueAtLocation(scalar, overlay);
					units[overlay.type] = _.object(overlay.units.map(function(unit) {
						return [unit.label, µ.formatScalar(scalar, unit)];
					}));
				}

				// Draw location mark.
				var path = d3.geo.path().projection(globe.projection).pointRadius(7);
				var mark = d3.select(".location-mark");
				if(!mark.node()) {
					mark = d3.select("#foreground").append("path").attr("class", "location-mark");
				}
				mark.datum({
					type: "Point",
					coordinates: [λ, φ]
				}).attr("d", path);

				if(bridge.showLocationDetails) {
					bridge.showLocationDetails(coordString, JSON.stringify(units));
				}
			}

			function saveLocation(point, coord) {
				var λ = +coord[0],
					φ = +coord[1],
					field = fieldAgent.value();
				if(point && field && !field.isInsideBoundary(point[0], point[1])) {
					return;
				}
				if(λ === λ && φ === φ) {
					configuration.save({
						loc: [λ, φ]
					});
				}
			}

			function removeLocation() {
				configuration.save({
					loc: null
				});
			}

			function stopCurrentAnimation(alsoClearCanvas) {
				animatorAgent.cancel();
				if(alsoClearCanvas) {
					µ.clearCanvas(d3.select("#animation").node());
				}
			}

			function highlightWhen(elementId, highlightAttrList) {
				// CONSIDER: the reasons requiring highlightAttrList need to be rethought...
				configuration.on("change", function(model) {
					var attr = model.attributes,
						highlighted = false;
					highlightAttrList.forEach(function(expectedAttr) {
						highlighted |= _.isEqual(_.pick(attr, _.keys(expectedAttr)), expectedAttr);
					});
					d3.select(elementId).classed("highlighted", highlighted);
				});
			}

			/**
			 * Registers a click event handler for the specified DOM element which modifies the configuration to have
			 * the attributes represented by newAttr. An event listener is also registered for configuration change events,
			 * so when a change occurs the button becomes highlighted (i.e., class ".highlighted" is assigned or removed) if
			 * the configuration matches the attributes for this button. The set of attributes used for the matching is taken
			 * from newAttr, unless a custom set of key+values is provided.
			 */
			function bindButtonToConfiguration(elementId, newAttr, highlightAttrList) {
				highlightAttrList = highlightAttrList || [newAttr];
				d3.select(elementId).on("click", function() {
					if(d3.select(elementId).classed("disabled")) return;
					configuration.save(newAttr);
				});
				highlightWhen(elementId, highlightAttrList);
			}

			function reportSponsorClick(type) {
				if(_.isFunction(window.ga)) {
					window.ga("send", "event", "sponsor", type);
				}
			}

			function fillScreen() {
				d3.selectAll(".fill-screen").attr("width", view.width).attr("height", view.height);
			}

			/**
			 * Registers all event handlers to bind components and page elements together. There must be a cleaner
			 * way to accomplish this...
			 */
			function init() {
				report.status("Initializing...");

				d3.select("#sponsor-link")
					.attr("target", µ.isEmbeddedInIFrame() && µ.siteInstance() !== "tara" ? "_new" : null)
					.on("click", _.partial(reportSponsorClick, "click"))
					.on("contextmenu", _.partial(reportSponsorClick, "right-click"));
				d3.select("#sponsor-hide").on("click", function() {
					d3.select("#sponsor").classed("invisible", true);
				});
				d3.select("#notice-hide").on("click", function() {
					d3.select("#notice").classed("invisible", true);
				});
				d3.select("#settings-show").select(".text-button").on("click", function() {
					d3.select("#settings-wrap").classed("invisible", false);
					if(!d3.select("#menu").classed("invisible")) { // hide menu if open
						earth.click("show-menu");
					}
				});
				d3.select("#settings-hide").on("click", function() {
					d3.select("#settings-wrap").classed("invisible", true);
				});
				! function() {
					// highlight active lang
					var lang = d3.select("html").attr("lang");
					d3.select("#settings").selectAll("a[lang='" + lang + "']").classed("highlighted", true);

					// show preferred lang
					var best = require("./lang").best(window.navigator);
					if(best !== lang) {
						d3.select("#settings-show").select("a[lang='" + best + "']").classed("invisible", false);
					}
				}();

				fillScreen();
				// Adjust size of the scale canvas to fill the width of the menu to the right of the label.
				var label = d3.select("#scale-label").node();
				d3.select("#scale")
					.attr("width", (d3.select("#menu").node().offsetWidth - label.offsetWidth) * 0.95)
					.attr("height", label.offsetHeight / 2);

				d3.select("#show-menu").on("click", function() {
					if(µ.isEmbeddedInIFrame() && !µ.isKioskMode() && µ.siteInstance() !== "tara") {
						window.open("https://earth.nullschool.net/" + window.location.hash, "_blank");
					} else {
						var opening = d3.select("#menu").classed("invisible");
						d3.select("#menu").classed("invisible", !opening);
						if(opening && _.isFunction(window.ga)) {
							window.ga("send", "event", "menu", "open");
						}
					}
				});

				if(µ.isFF()) {
					// Workaround FF performance issue of slow click behavior on map having thick coastlines.
					d3.select("#display").classed("firefox", true);
				}

				// Tweak document to distinguish CSS styling between touch and non-touch environments. Hacky hack.
				if("ontouchstart" in document.documentElement) {
					d3.select(document).on("touchstart", function() {}); // this hack enables :active pseudoclass
				} else {
					d3.select(document.documentElement).classed("no-touch", true); // to filter styles problematic for touch
				}

				function updateLangLinks() {
					// Change all lang links to retain the current hash fragment so stuff stays the same when clicked.
					d3.selectAll("a[lang]").attr("href", function() {
						var e = d3.select(this),
							link = e.attr("data-link");
						return link ? link + window.location.hash : e.attr("href");
					});
				}

				updateLangLinks();

				// Bind configuration to URL bar changes.
				d3.select(window).on("hashchange", function() {
					log.debug("hashchange");
					updateLangLinks();
					configuration.fetch({
						trigger: "hashchange"
					});
				});

				configuration.on("change", function() {
					report.reset();
					buttonStateNotifier.submit(function() { // use agent to delay notification until button modification is done
						var highlighted = [],
							disabled = [];
						d3.selectAll(".highlighted").each(function() {
							highlighted.push(this.id);
						});
						d3.selectAll(".disabled").each(function() {
							disabled.push(this.id);
						});
						if(bridge.buttonStateChange) {
							bridge.buttonStateChange(JSON.stringify(highlighted), JSON.stringify(disabled));
						}
					});
				});

				var first = true;
				configuration.on("change", function(model) {
					if(!_.isFunction(window.ga) || first) return(first = false);
					var changed = model.changedAttributes(),
						attr = _.clone(model.attributes);
					if(_.has(changed, "projection")) {
						window.ga("send", "event", "projection", attr.projection);
					}
					if(_.has(changed, "overlayType")) {
						window.ga("send", "event", "overlay", attr.overlayType);
					}
					if(_.has(changed, "param") || _.has(changed, "surface") || _.has(changed, "level")) {
						window.ga("send", "event", "layer", [attr.param, attr.surface, attr.level].join("-"));
					}
				});

				meshAgent.listenTo(configuration, "change:topology", function(context, attr) {
					meshAgent.submit(buildMesh, attr);
				});

				globeAgent.listenTo(configuration, "change:projection", function(source, attr) {
					globeAgent.submit(buildGlobe, attr);
				});

				gridAgent.listenTo(configuration, "change", function() {
					var changed = _.keys(configuration.changedAttributes()),
						rebuildRequired = false,
						isNewPrimaryGrid = false;

					// Build a new primary grid if any related attributes have changed.
					if(_.intersection(changed, ["date", "param", "surface", "level", "hd"]).length > 0) {
						rebuildRequired = true;
						isNewPrimaryGrid = true;
					}
					// Build a new overlay grid if the new overlay type is different from the current one.
					var overlayType = configuration.get("overlayType") || "default";
					if(_.indexOf(changed, "overlayType") >= 0 && overlayType !== "off") {
						var grids = (gridAgent.value() || {}),
							primary = grids.primaryGrid,
							overlay = grids.overlayGrid;
						if(!overlay) {
							// Do a rebuild if we have no overlay grid.
							rebuildRequired = true;
						} else if(overlay.type !== overlayType && !(overlayType === "default" && primary === overlay)) {
							// Do a rebuild if the types are different.
							rebuildRequired = true;
						}
					}

					if(rebuildRequired) {
						gridAgent.submit(buildGrids, isNewPrimaryGrid);
					}
				});
				//        gridAgent.on("submit", function() {
				//            showGridDetails(null);
				//        });
				gridAgent.on("update", function(grids) {
					showGridDetails(grids);
				});
				d3.select("#toggle-zone").on("click", function() {
					d3.select("#data-date").classed("local", !d3.select("#data-date").classed("local"));
					showDate(gridAgent.cancel.requested ? null : gridAgent.value());
				});

				function startRendering() {
					rendererAgent.submit(buildRenderer, meshAgent.value(), globeAgent.value());
				}
				rendererAgent.listenTo(meshAgent, "update", startRendering);
				rendererAgent.listenTo(globeAgent, "update", startRendering);

				function startInterpolation() {
					fieldAgent.submit(interpolateField, globeAgent.value(), gridAgent.value());
				}

				function cancelInterpolation() {
					fieldAgent.cancel();
					overlayGridAgent.cancel();
				}
				fieldAgent.listenTo(gridAgent, "update", startInterpolation);
				fieldAgent.listenTo(rendererAgent, "render", startInterpolation);
				fieldAgent.listenTo(rendererAgent, "start", cancelInterpolation);
				fieldAgent.listenTo(rendererAgent, "redraw", cancelInterpolation);

				//argoAgent.listenTo(globeAgent, "update", function() {
				//    argoAgent.submit(addArgo);
				//});
				////argoAgent.listenTo(rendererAgent, "render", function() {
				////    argoAgent.submit(addArgo);
				////});
				//argoAgent.listenTo(configuration, "change:argoFloat", function() {
				//    argoAgent.submit(addArgo);
				//});

				animatorAgent.listenTo(fieldAgent, "update", function(field) {
					animatorAgent.submit(animate, globeAgent.value(), field, gridAgent.value());
				});
				animatorAgent.listenTo(rendererAgent, "start", _.partial(stopCurrentAnimation, true));
				animatorAgent.listenTo(gridAgent, "submit", _.partial(stopCurrentAnimation, false));
				animatorAgent.listenTo(fieldAgent, "submit", _.partial(stopCurrentAnimation, false));

				overlayAgent.listenTo(fieldAgent, "update", function() {
					overlayAgent.submit(
						drawOverlay,
						fieldAgent.value(),
						configuration.get("overlayType"),
						configuration.get("animate"));
				});
				overlayAgent.listenTo(rendererAgent, "start", function() {
					// Immediately clear overlay. Chrome will wait until mouse pauses before agent is triggered. :(
					µ.clearCanvas(d3.select("#overlay").node());
					overlayAgent.submit(drawOverlay, fieldAgent.value(), null, null);
				});
				overlayAgent.listenTo(configuration, "change", function() {
					var changed = _.keys(configuration.changedAttributes());
					// if only overlay relevant flags have changed...
					if(_.intersection(changed, ["overlayType", "showGridPoints", "animate"]).length > 0) {
						overlayAgent.submit(
							drawOverlay,
							fieldAgent.value(),
							configuration.get("overlayType"),
							configuration.get("animate"));
					}
				});

				configuration.on("change:animate", function(context, enabled) {
					if(!enabled) {
						stopCurrentAnimation(true);
					} else {
						startInterpolation();
					}
				});

				// Add event handlers for showing, updating, and removing location details.
				inputController.on("click", saveLocation);
				d3.select("#location-close").on("click", removeLocation);
				fieldAgent.on("update", updateLocation);
				configuration.on("change:loc", updateLocation);

				// Modify menu depending on what mode we're in.
				configuration.on("change:param", function(context, mode) {
					d3.selectAll(".ocean-mode").classed("invisible", mode !== "ocean");
					d3.selectAll(".wind-mode").classed("invisible", mode !== "wind")
						.selectAll(".text-button").classed("disabled", mode !== "wind"); // hack: hot keys cannot change height
					d3.selectAll(".chem-mode").classed("invisible", mode !== "chem");
					d3.selectAll(".particulates-mode").classed("invisible", mode !== "particulates");
					//d3.selectAll(".argo-mode").classed("invisible", mode !== "argo");
					//d3.select("#notice").classed("invisible", true);
					switch(mode) {
						case "chem":
						case "particulates":
							//d3.select("#notice").classed("invisible", false);
							// fall through
						case "wind":
							d3.select("#nav-backward-more").attr("title", "-1 Day");
							d3.select("#nav-backward").attr("title", "-3 Hours");
							d3.select("#nav-forward").attr("title", "+3 Hours");
							d3.select("#nav-forward-more").attr("title", "+1 Day");
							break;
						case "ocean":
							d3.select("#nav-backward-more").attr("title", "-1 Month");
							d3.select("#nav-backward").attr("title", "-5 Days");
							d3.select("#nav-forward").attr("title", "+5 Days");
							d3.select("#nav-forward-more").attr("title", "+1 Month");
							break;
					}
				});

				// Add handlers for mode buttons.
				d3.select("#air-mode").on("click", function() {
					if(configuration.get("param") !== "wind") {
						configuration.save({
							param: "wind",
							surface: "surface",
							level: "level",
							overlayType: "default"
						});
					}
				});
				configuration.on("change:param", function(x, param) {
					d3.select("#air-mode").classed("highlighted", param === "wind");
				});
				d3.select("#ocean-mode").on("click", function() {
					if(configuration.get("param") !== "ocean") {
						// When switching between modes, there may be no associated data for the current date. So we need
						// find the closest available according to the catalog. This is not necessary if date is "current".
						// UNDONE: this code is annoying. should be easier to get date for closest ocean product.
						var ocean = {
							param: "ocean",
							surface: "primary",
							level: "waves",
							overlayType: "significant_wave_height"
						};
						var attr = _.clone(configuration.attributes);
						if(attr.date === "current") {
							configuration.save(ocean);
						} else {
							when.all(products.productsFor(_.extend(attr, ocean))).spread(function(product) {
								if(product.date()) {
									configuration.save(_.extend(ocean, µ.dateToConfig(product.date())));
								}
							}).otherwise(report.error);
						}
					}
				});
				configuration.on("change:param", function(x, param) {
					d3.select("#ocean-mode").classed("highlighted", param === "ocean");
				});

				d3.select("#chem-mode").on("click", function() {
					if(configuration.get("param") !== "chem") {
						configuration.save({
							param: "chem",
							surface: "surface",
							level: "level",
							overlayType: "cosc"
						});
					}
				});
				configuration.on("change:param", function(x, param) {
					d3.select("#chem-mode").classed("highlighted", param === "chem");
				});

				d3.select("#particulates-mode").on("click", function() {
					if(configuration.get("param") !== "particulates") {
						configuration.save({
							param: "particulates",
							surface: "surface",
							level: "level",
							overlayType: "duexttau"
						});
					}
				});
				configuration.on("change:param", function(x, param) {
					d3.select("#particulates-mode").classed("highlighted", param === "particulates");
				});

				/*
				        d3.select("#argo-mode").on("click", function() {
				            if (configuration.get("param") !== "argo") {
				                //         param surface
				                // current/ocean/surface/currents/overlay=sea_surface_temp
				                configuration.save({param: "argo", surface: "surface", level: "currents", overlayType: "sea_surface_temp"});
				            }
				        });
				        configuration.on("change:param", function(x, param) {
				            d3.select("#argo-mode").classed("highlighted", param === "argo");
				        });
				*/

				// Add logic to disable buttons that are incompatible with each other.
				configuration.on("change:overlayType", function(x, ot) {
					d3.selectAll(".surface").classed("disabled", function() {
						if(configuration.get("param") !== "wind") {
							return true;
						}
						return this.getAttribute("id") === "surface-level" ?
							ot === "wind_power_density" :
							ot === "misery_index";
					});
				});
				configuration.on("change:surface", function(x, s) {
					d3.select("#wind_power_density").classed("disabled", s === "surface");
				});

				// Add event handlers for the time navigation buttons.
				d3.select("#nav-backward-more").on("click", _.partial(navigate, -10));
				d3.select("#nav-forward-more").on("click", _.partial(navigate, +10));
				d3.select("#nav-backward").on("click", _.partial(navigate, -1));
				d3.select("#nav-forward").on("click", _.partial(navigate, +1));
				d3.select("#nav-now").on("click", function() {
					configuration.save({
						date: "current"
					});
				});

				d3.select("#option-show-grid").on("click", function() {
					configuration.save({
						showGridPoints: !configuration.get("showGridPoints")
					});
				});
				configuration.on("change:showGridPoints", function(x, showGridPoints) {
					d3.select("#option-show-grid").classed("highlighted", showGridPoints);
				});

				d3.select("#animate-start").on("click", function() {
					configuration.save({
						animate: !configuration.get("animate")
					});
				});
				configuration.on("change:animate", function(x, animate) {
					d3.select("#animate-start").classed("highlighted", animate);
				});

				d3.select("#hd").on("click", function() {
					configuration.save({
						hd: !configuration.get("hd")
					});
				});
				configuration.on("change:hd", function(x, hd) {
					d3.select("#hd").classed("highlighted", hd);
				});

				// Add handlers for all wind level buttons.
				d3.selectAll(".surface").each(function() {
					var id = this.id,
						parts = id.split("-");
					bindButtonToConfiguration("#" + id, {
						param: "wind",
						surface: parts[0],
						level: parts[1]
					});
				});

				// Add handlers for ocean animation types.
				bindButtonToConfiguration(
					"#animate-currents", {
						param: "ocean",
						surface: "surface",
						level: "currents",
						animate: true,
						overlayType: "default"
					}, [{
						param: "ocean",
						surface: "surface",
						level: "currents"
					}]);
				bindButtonToConfiguration(
					"#animate-waves", {
						param: "ocean",
						surface: "primary",
						level: "waves",
						animate: true,
						overlayType: "default"
					}, [{
						param: "ocean",
						surface: "primary",
						level: "waves"
					}]);

				// Add handlers for all overlay buttons.
				products.overlayTypes().forEach(function(type) {
					var newAttr = {
							overlayType: type
						},
						highlightAttrList = [newAttr];
					if(type === "misery_index") {
						_.extend(newAttr, {
							surface: "surface",
							level: "level"
						});
					} else if(type === "currents") {
						highlightAttrList.push({
							param: "ocean",
							surface: "surface",
							level: "currents",
							overlayType: "default"
						});
					} else if(type === "primary_waves") {
						highlightAttrList.push({
							param: "ocean",
							surface: "primary",
							level: "waves",
							overlayType: "default"
						});
					}
					bindButtonToConfiguration("#" + type, newAttr, highlightAttrList);
				});
				bindButtonToConfiguration("#wind", {
					param: "wind",
					overlayType: "default"
				});
				bindButtonToConfiguration("#no-overlay", {
					overlayType: "off"
				});

				/*
				        bindButtonToConfiguration("#argo-planned", {argoFloat: "planned"});
				        bindButtonToConfiguration("#argo-recent", {argoFloat: "recent"});
				        bindButtonToConfiguration("#argo-active", {argoFloat: "active"});
				        bindButtonToConfiguration("#argo-dead", {argoFloat: "dead"});
				        bindButtonToConfiguration("#argo-none", {argoFloat: null});
				*/

				// Add handlers for all projection buttons.
				_.keys(globes).forEach(function(p) {
					bindButtonToConfiguration("#" + p, {
						projection: p,
						orientation: ""
					}, [{
						projection: p
					}]);
				});

				// When touch device changes between portrait and landscape, rebuild globe using the new view size.
				d3.select(window).on("orientationchange", function() {
					window.scrollTo(0, 0);
					view = µ.view();
					fillScreen();
					globeAgent.submit(buildGlobe, configuration.get("projection"));
				});

				if(µ.isKioskMode()) {
					d3.selectAll(".kiosk").classed("invisible", true);
				} else {
					setTimeout(function() {
						d3.select("#menu-ham").transition().style("opacity", "0").remove();
					}, 15 * 1000);
				}

				var pressure = [
					"surface-level",
					"isobaric-1000hPa",
					"isobaric-850hPa",
					"isobaric-700hPa",
					"isobaric-500hPa",
					"isobaric-250hPa",
					"isobaric-70hPa",
					"isobaric-10hPa"
				];

				function pressureLevel() {
					var attr = configuration.attributes,
						key = attr.surface + "-" + attr.level;
					return attr.param === "wind" ? _.indexOf(pressure, key) : -1;
				}

				function clearDialog() {
					if(!d3.select("#settings-wrap").classed("invisible")) {
						earth.click("settings-hide");
						return true;
					}
					if(!d3.select("#location-close").classed("invisible")) {
						earth.click("location-close");
						return true;
					}
					if(!d3.select("#menu").classed("invisible")) {
						earth.click("show-menu");
						return true;
					}
					if(!d3.select("#sponsor").classed("invisible")) {
						earth.click("sponsor-hide");
						return true;
					}
				}

				d3.select("body").on("keydown", function() {
					var e = d3.event,
						key = keyboard.key(e);
					if(e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
						return;
					}
					switch(key) {
						case "Escape":
							if(!clearDialog()) return;
							break;
						default:
							return;
							// Never bind to keys used for search in various browsers:  /.',
					}
					e.preventDefault(); // stops FF from showing search bar
				});

				d3.select("body").on("keypress", function() {
					var e = d3.event,
						key = keyboard.key(e);
					if(e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey) {
						return;
					}
					switch(key) {
						case "e":
							earth.click("show-menu");
							break;
						case "I":
							earth.click(pressure[pressure.length - 1]);
							break;
						case "i":
							earth.click(pressure[pressureLevel() + 1]);
							break;
						case "m":
							earth.click(pressure[pressureLevel() - 1]);
							break;
						case "M":
							earth.click(pressure[0]);
							break;
						case "J":
							earth.click("nav-backward-more");
							break;
						case "j":
							earth.click("nav-backward");
							break;
						case "k":
							{
								earth.click("nav-forward");
								// var coord = earth.orient();
								// coord[0] += 45;
								// earth.orient(coord);
							}
							break;
						case "K":
							earth.click("nav-forward-more");
							break;
						case "n":
							earth.click("nav-now");
							break;
						case "p":
							earth.click("animate-start");
							break;
						case "g":
							earth.click("option-show-grid");
							break;
						case "Z":
							earth.click("hd");
							break;
						default:
							return;
							// Never bind to keys used for search in various browsers:  /.',
							// Never bind to cursor keys (used for scrolling during zoom).
					}
					e.preventDefault(); // stops FF from showing search bar
				});

				setTimeout(function() {
					var start = Date.now();
					var msg = require("./webglOverlay").checkCompatibility() || "ok";
					log.debug("check webgl (" + (Date.now() - start) + "ms): " + msg);
					if(_.isFunction(window.ga)) {
						window.ga("send", "event", "webgl", msg);
					}
				}, 0);
			}

			function start() {
				// Everything is now set up, so load configuration from the hash fragment and kick off change events.
				configuration.fetch();
			}

			when(true).then(init).then(start).otherwise(report.error);

			function activeProduct() {
				return(gridAgent.value() || {}).overlayGrid;
			}

			_.extend(earth, {

				url: function() {
					return window.location.href.toString();
				},

				scale: {

					/**
					 * @param {Number} pct percentage in the range [0, 100] (inclusive)
					 * @returns {String} JSON encoded map of units to values, or "{}" if no product is active. For example:
					 *     {
					 *         "wind": {
					 *             "m/s" : "100° @ 7.3",
					 *             "kn"  : "100° @ 14",
					 *             "mph" : "100° @ 16"
					 *         }
					 *     }
					 */
					valueAt: function(pct) {
						var product = activeProduct(),
							result = {};
						if(product) {
							var group = {},
								bounds = product.scale.bounds,
								value = µ.spread(pct / 100, bounds[0], bounds[1]);
							product.units.forEach(function(unit) {
								group[unit.label] = µ.formatScalar(value, unit);
							});
							result[product.type] = group;
						}
						return JSON.stringify(result);
					},

					/**
					 * @param {Number} size the size of the palette.
					 * @returns {String} a JSON encoded array of colors, "[r0, g0, b0, r1, g1, b1, ..., rn-1, gn-1, bn-1]",
					 *          where n is the size of the palette. The result is empty "{}" if no product is active, otherwise
					 *          the length will be size*3.
					 *          {
					 *              "wind": [0, 0, 0, ..., 255, 255, 255]
					 *          }
					 */
					palette: function(size) {
						var product = activeProduct(),
							result = {};
						if(product) {
							var buffer = [],
								scale = product.scale,
								bounds = scale.bounds,
								m = size > 1 ? 1 / (size - 1) : 1;
							for(var i = 0; i < size; i++) {
								var v = µ.spread(i * m, bounds[0], bounds[1]),
									color = scale.gradient(v, 0);
								buffer.push(color[0], color[1], color[2]);
							}
							result[product.type] = buffer;
						}
						return JSON.stringify(result);
					}
				},

				/**
				 * Navigate current product to the specified date. Return the expected date of that product as milliseconds,
				 * or 0 if there is no product, or the specified date is not valid.
				 *
				 * @param {*} date milliseconds from unix epoch or string in format "yyyy-MM-ddThh:mm:ssZ".
				 * @returns {Number} date of the product best matching the specified date as milliseconds from epoch,
				 *                   or 0 if no product can be found.
				 */
				navToDate: function(date) {
					var product = activeProduct(),
						target = new Date(date);
					if(!product || _.isNaN(target.getUTCFullYear())) return 0;
					target = product.navigateTo(utc.parts(target));
					if(!target) return 0;
					_.defer(function() {
						configuration.save(µ.dateToConfig(target)); // defer all side effects
					});
					return utc.date(target).getTime();
				},

				/**
				 * Triggers a click event on the specified button.
				 *
				 * @param {String} id the button id.
				 */
				click: function(id) {
					// side effects means we need to defer
					_.defer(function() {
						var node = d3.select("#" + id).node();
						return node !== null ? node.dispatchEvent(µ.newClickEvent()) : undefined;
					});
				},

				/**
				 * Shows location indicator at specified coordinates.
				 *
				 * @param {Number} lon
				 * @param {Number} lat
				 */
				locate: function(lon, lat) {
					// side effects means we need to defer
					_.defer(locate, lon, lat);
				},

				orient: function(coord) { // HACK for now. What's the best way to do this?
					_.defer(function() {
						var globe = globeAgent.value();
						if(coord === undefined) {
							return globe.projection.rotate();
						}
						globe.projection.rotate(coord);
						configuration.save({
							orientation: globe.orientation()
						}); // triggers reorientation
					});
				},

				/**
				 * Stop animation.
				 */
				stop: function() {
					// side effects means we need to defer
					_.defer(function() {
						configuration.save({
							animate: false
						});
					});
				},

				/**
				 * Forcefully ends the current move operation, if any.
				 */
				cancelMove: function() {
					inputController.cancelMove();
				}
			});
		};

	}, {
		"./globes": 31,
		"./keyboard": 35,
		"./lang": 36,
		"./micro": 39,
		"./products": 59,
		"./utc": 61,
		"./webglOverlay": 62,
		"backbone": 1,
		"d3": 3,
		"topojson": 4,
		"underscore": 5,
		"when": 23
	}],
	30: [function(require, module, exports) {
		"use strict";

		/*
		 * webglOverlay: webgl implementation of color overlay
		 *
		 * Copyright (c) 2016 Cameron Beccario
		 */

		// http://stackoverflow.com/questions/23598471/how-do-i-clean-up-and-unload-a-webgl-canvas-context-from-gpu-after-use

		var ƒ = module.exports = {};

		/** @returns {HTMLCanvasElement} */
		ƒ.createCanvas = function() {
			return /** @type {HTMLCanvasElement} */ document.createElement("canvas");
		};

		/**
		 * @param {HTMLCanvasElement} canvas DOM element
		 * @param {Object?} attributes WebGL context attributes
		 * @returns {WebGLRenderingContext}
		 */
		ƒ.getWebGL = function(canvas, attributes) {
			return /** @type {WebGLRenderingContext} */ canvas.getContext("webgl", attributes) ||
				/** @type {WebGLRenderingContext} */
				canvas.getContext("experimental-webgl", attributes);
		};

	}, {}],
	31: [function(require, module, exports) {
		/**
		 * globes - a set of models of the earth, each having their own kind of projection and onscreen behavior.
		 *
		 * Copyright (c) 2016 Cameron Beccario
		 *
		 * For a free version of this project, see https://github.com/cambecc/earth
		 */
		! function() {
			"use strict";

			var globes = module.exports = {};

			var d3 = require("d3");
			var _ = require("underscore");
			var µ = require("./micro");
			var ortho = require("./projection/orthographic");

			/**
			 * @returns {Array} rotation of globe to current position of the user. Aside from asking for geolocation,
			 *          which user may reject, there is not much available except timezone. Better than nothing.
			 */
			function currentPosition() {
				var λ = µ.floorMod(new Date().getTimezoneOffset() / 4, 360); // 24 hours * 60 min / 4 === 360 degrees
				return [λ, 0];
			}

			function ensureNumber(num, fallback) {
				return _.isFinite(num) || num === Infinity || num === -Infinity ? num : fallback;
			}

			/**
			 * @param bounds the projection bounds: [[x0, y0], [x1, y1]]
			 * @param view the view bounds {width:, height:}
			 * @returns {Object} the projection bounds clamped to the specified view.
			 */
			function clampedBounds(bounds, view) {
				var upperLeft = bounds[0];
				var lowerRight = bounds[1];
				var x = Math.max(Math.floor(ensureNumber(upperLeft[0], 0)), 0);
				var y = Math.max(Math.floor(ensureNumber(upperLeft[1], 0)), 0);
				var xMax = Math.min(Math.ceil(ensureNumber(lowerRight[0], view.width)), view.width - 1);
				var yMax = Math.min(Math.ceil(ensureNumber(lowerRight[1], view.height)), view.height - 1);
				return {
					x: x,
					y: y,
					xMax: xMax,
					yMax: yMax,
					width: xMax - x + 1,
					height: yMax - y + 1
				};
			}

			/**
			 * Returns a globe object with standard behavior. At least the newProjection method must be overridden to
			 * be functional.
			 */
			function standardGlobe() {
				return {
					/**
					 * This globe's current D3 projection.
					 */
					projection: null,

					/**
					 * @param view the size of the view as {width:, height:}.
					 * @returns {Object} a new D3 projection of this globe appropriate for the specified view port.
					 */
					newProjection: function(view) {
						throw new Error("method must be overridden");
					},

					/**
					 * Hand-optimized projection if available, otherwise the normal d3 projection.
					 */
					optimizedProjection: function() {
						return this.projection;
					},

					/**
					 * @param view the size of the view as {width:, height:}.
					 * @returns {{x: Number, y: Number, xMax: Number, yMax: Number, width: Number, height: Number}}
					 *          the bounds of the current projection clamped to the specified view.
					 */
					bounds: function(view) {
						return clampedBounds(d3.geo.path().projection(this.projection).bounds({
							type: "Sphere"
						}), view);
					},

					/**
					 * @param view the size of the view as {width:, height:}.
					 * @returns {Number} the projection scale at which the entire globe fits within the specified view.
					 */
					fit: function(view) {
						if(µ.isEmbeddedInIFrame() && µ.siteInstance() === "tara") {
							return 700; // HACK: to get things the right size in the iframe.
						}
						var defaultProjection = this.newProjection(view);
						var bounds = d3.geo.path().projection(defaultProjection).bounds({
							type: "Sphere"
						});
						var hScale = (bounds[1][0] - bounds[0][0]) / defaultProjection.scale();
						var vScale = (bounds[1][1] - bounds[0][1]) / defaultProjection.scale();
						return Math.min(view.width / hScale, view.height / vScale) * 0.9;
					},

					/**
					 * @param view the size of the view as {width:, height:}.
					 * @returns {Array} the projection transform at which the globe is centered within the specified view.
					 */
					center: function(view) {
						return [view.width / 2, view.height / 2];
					},

					/**
					 * @returns {Array} the range at which this globe can be zoomed.
					 */
					scaleExtent: function() {
						return [100, 3000];
					},

					/**
					 * Returns the current orientation of this globe as a string. If the arguments are specified,
					 * mutates this globe to match the specified orientation string, usually in the form "lat,lon,scale".
					 *
					 * @param [o] the orientation string
					 * @param [view] the size of the view as {width:, height:}.
					 */
					orientation: function(o, view) {
						var projection = this.projection,
							rotate = projection.rotate();
						if(view) {
							var parts = _.isString(o) ? o.split(",") : [];
							var λ = +parts[0],
								φ = +parts[1],
								scale = +parts[2];
							var extent = this.scaleExtent();
							projection.rotate(_.isFinite(λ) && _.isFinite(φ) ? [-λ, -φ, rotate[2]] :
								this.newProjection(view).rotate());
							projection.scale(_.isFinite(scale) ? µ.clamp(scale, extent[0], extent[1]) : this.fit(view));
							projection.translate(this.center(view));
							return this;
						}
						return [(-rotate[0]).toFixed(2), (-rotate[1]).toFixed(2), Math.round(projection.scale())].join(",");
					},

					/**
					 * Returns an object that mutates this globe's current projection during a drag/zoom operation.
					 * Each drag/zoom event invokes the move() method, and when the move is complete, the end() method
					 * is invoked.
					 *
					 * @param startMouse starting mouse position.
					 * @param startScale starting scale.
					 */
					manipulator: function(startMouse, startScale) {
						var projection = this.projection;
						var sensitivity = 60 / startScale; // seems to provide a good drag scaling factor
						var rotation = [projection.rotate()[0] / sensitivity, -projection.rotate()[1] / sensitivity];
						var original = projection.precision();
						projection.precision(original * 10);
						return {
							move: function(mouse, scale) {
								if(mouse) {
									var xd = mouse[0] - startMouse[0] + rotation[0];
									var yd = mouse[1] - startMouse[1] + rotation[1];
									projection.rotate([xd * sensitivity, -yd * sensitivity, projection.rotate()[2]]);
								}
								projection.scale(scale);
							},
							end: function() {
								projection.precision(original);
							}
						};
					},

					/**
					 * @returns {Array} the transform to apply, if any, to orient this globe to the specified coordinates.
					 */
					locate: function(coord) {
						return null;
					},

					/**
					 * Draws a polygon on the specified context of this globe's boundary.
					 * @param context a Canvas element's 2d context.
					 * @returns the context
					 */
					defineMask: function(context) {
						d3.geo.path().projection(this.projection).context(context)({
							type: "Sphere"
						});
						return context;
					},

					/**
					 * Appends the SVG elements that render this globe.
					 * @param mapSvg the primary map SVG container.
					 * @param foregroundSvg the foreground SVG container.
					 */
					defineMap: function(mapSvg, foregroundSvg) {
						var path = d3.geo.path().projection(this.projection);
						var defs = mapSvg.append("defs");
						defs.append("path")
							.attr("id", "sphere")
							.datum({
								type: "Sphere"
							})
							.attr("d", path);
						var mask = defs.append("mask")
							.attr("id", "mask");
						mask.append("rect")
							.attr("fill", "white")
							.attr("width", "100%")
							.attr("height", "100%");
						mask.append("use")
							.attr("xlink:href", "#sphere");
						mapSvg.append("use")
							.classed("background-sphere", true)
							.attr("xlink:href", "#sphere");
						mapSvg.append("path")
							.classed("graticule", true)
							.datum(d3.geo.graticule())
							.attr("d", path);
						mapSvg.append("path")
							.classed("hemisphere", true)
							.datum(d3.geo.graticule().minorStep([0, 90]).majorStep([0, 90]))
							.attr("d", path);
						mapSvg.append("path")
							.classed("coastline", true);
						mapSvg.append("path")
							.classed("lakes", true);
						mapSvg.append("path")
							.classed("rivers", true);
						foregroundSvg.append("use")
							.classed("foreground-sphere", true)
							.attr("xlink:href", "#sphere");
					},
					beforeMove: function(mapSvg, foregroundSvg) {
						// Remove SVG elements that make move animation too slow.
						foregroundSvg.selectAll("#atmos").remove();
					},
					afterMove: function(mapSvg, foregroundSvg) {
						foregroundSvg.append("use")
							.classed("foreground-sphere", true)
							.attr("xlink:href", "#sphere")
							.attr("id", "atmos")
							.attr("mask", "url(#mask)");
					}
				};
			}

			function newGlobe(source, view) {
				var result = _.extend(standardGlobe(), source);
				result.projection = result.newProjection(view);
				return result;
			}

			// ============================================================================================

			globes.atlantis = function() {
				return newGlobe({
					newProjection: function() {
						return d3.geo.mollweide().rotate([30, -45, 90]).precision(0.1);
					}
				});
			};

			globes.azimuthal_equidistant = function() {
				return newGlobe({
					newProjection: function() {
						return d3.geo.azimuthalEquidistant().precision(0.1).rotate([0, -90]).clipAngle(180 - 0.001);
					}
				});
			};

			globes.conic_equidistant = function() {
				return newGlobe({
					newProjection: function() {
						return d3.geo.conicEquidistant().rotate(currentPosition()).precision(0.1);
					},
					center: function(view) {
						return [view.width / 2, view.height / 2 + view.height * 0.065];
					}
				});
			};

			globes.equirectangular = function() {
				return newGlobe({
					newProjection: function() {
						return d3.geo.equirectangular().rotate(currentPosition()).precision(0.1);
					}
				});
			};

			globes.orthographic = function() {
				return newGlobe({
					newProjection: function() {
						return d3.geo.orthographic().rotate(currentPosition()).precision(0.1).clipAngle(90);
					},
					optimizedProjection: function() {
						return ortho.fromD3(this.projection);
					},
					defineMap: function(mapSvg, foregroundSvg) {
						var path = d3.geo.path().projection(this.projection);
						var defs = mapSvg.append("defs");
						var gradientFill = defs.append("radialGradient")
							.attr("id", "orthographic-fill")
							.attr("gradientUnits", "objectBoundingBox")
							.attr("cx", "50%").attr("cy", "49%").attr("r", "50%");
						gradientFill.append("stop").attr("stop-color", "#303030").attr("offset", "69%");
						gradientFill.append("stop").attr("stop-color", "#202020").attr("offset", "91%");
						gradientFill.append("stop").attr("stop-color", "#000005").attr("offset", "96%");
						defs.append("path")
							.attr("id", "sphere")
							.datum({
								type: "Sphere"
							})
							.attr("d", path);
						var mask = defs.append("mask")
							.attr("id", "mask");
						mask.append("rect")
							.attr("fill", "white")
							.attr("width", "100%")
							.attr("height", "100%");
						mask.append("use")
							.attr("xlink:href", "#sphere");
						mapSvg.append("use")
							.attr("xlink:href", "#sphere")
							.attr("fill", "url(#orthographic-fill)");
						mapSvg.append("path")
							.classed("graticule", true)
							.datum(d3.geo.graticule())
							.attr("d", path);
						mapSvg.append("path")
							.classed("hemisphere", true)
							.datum(d3.geo.graticule().minorStep([0, 90]).majorStep([0, 90]))
							.attr("d", path);
						mapSvg.append("path")
							.classed("coastline", true);
						mapSvg.append("path")
							.classed("lakes", true);
						mapSvg.append("path")
							.classed("rivers", true);
						foregroundSvg.append("use")
							.classed("foreground-sphere", true)
							.attr("xlink:href", "#sphere");
					},
					locate: function(coord) {
						return [-coord[0], -coord[1], this.projection.rotate()[2]];
					}
				});
			};

			globes.patterson = function() {
				return newGlobe({
					newProjection: function() {
						return d3.geo.patterson().precision(0.1);
					}
				});
			};

			globes.stereographic = function(view) {
				return newGlobe({
					newProjection: function(view) {
						return d3.geo.stereographic()
							.rotate([-43, -20])
							.precision(1.0)
							.clipAngle(180 - 0.0001)
							.clipExtent([
								[0, 0],
								[view.width, view.height]
							]);
					}
				}, view);
			};

			globes.waterman = function() {
				return newGlobe({
					newProjection: function() {
						return d3.geo.polyhedron.waterman().rotate([20, 0]).precision(0.1);
					},
					defineMap: function(mapSvg, foregroundSvg) {
						var path = d3.geo.path().projection(this.projection);
						var defs = mapSvg.append("defs");
						defs.append("path")
							.attr("id", "sphere")
							.datum({
								type: "Sphere"
							})
							.attr("d", path);
						defs.append("clipPath")
							.attr("id", "clip")
							.append("use")
							.attr("xlink:href", "#sphere");
						var mask = defs.append("mask")
							.attr("id", "mask");
						mask.append("rect")
							.attr("fill", "white")
							.attr("width", "100%")
							.attr("height", "100%");
						mask.append("use")
							.attr("xlink:href", "#sphere");
						mapSvg.append("use")
							.classed("background-sphere", true)
							.attr("xlink:href", "#sphere");
						mapSvg.append("path")
							.classed("graticule", true)
							.attr("clip-path", "url(#clip)")
							.datum(d3.geo.graticule())
							.attr("d", path);
						mapSvg.append("path")
							.classed("coastline", true)
							.attr("clip-path", "url(#clip)");
						mapSvg.append("path")
							.classed("lakes", true)
							.attr("clip-path", "url(#clip)");
						mapSvg.append("path")
							.classed("rivers", true)
							.attr("clip-path", "url(#clip)");
						foregroundSvg.append("use")
							.classed("foreground-sphere", true)
							.attr("xlink:href", "#sphere");
					}
				});
			};

			globes.winkel3 = function() {
				return newGlobe({
					newProjection: function() {
						return d3.geo.winkel3().precision(0.1);
					}
				});
			};

		}();

	}, {
		"./micro": 39,
		"./projection/orthographic": 60,
		"d3": 3,
		"underscore": 5
	}],
	32: [function(require, module, exports) {
		"use strict";

		var µ = require("../micro");

		/**
		 * Creates a rectangular geographic grid. Each axis defines the "start" value in degrees, "delta" degrees between
		 * ticks, and the "size" (number of ticks from the origin, inclusive). Positive deltas move eastward/northward. This
		 * example creates a full 1° x 1° grid covering the earth starting at the south pole:
		 *
		 *     λaxis: {start: 0, delta: 1, size: 360}    where λ in the range [0, 359]
		 *     φaxis: {start: -90, delta: 1, size: 181}  where φ in the range [-90, 90]
		 *
		 * A grid maps from [λ, φ] coordinates to grid point indices.
		 *
		 * @param λaxis longitude axis
		 * @param φaxis latitude axis
		 * @returns {*}
		 */
		module.exports = function rectangularGrid(λaxis, φaxis) {

			var λ0 = µ.decimalize(λaxis.start); // lon origin
			var φ0 = µ.decimalize(φaxis.start); // lat origin which should be on range [-90, 90]
			var Δλ = µ.decimalize(λaxis.delta); // distance between lon points
			var Δφ = µ.decimalize(φaxis.delta); // distance between lat points
			var nx = Math.floor(λaxis.size); // number of lon points
			var ny = Math.floor(φaxis.size); // number of lat points
			var np = nx * ny; // total number of points

			var isCylinder = Math.floor(nx * Δλ) >= 360; // true if the grid forms a cylinder

			//function iterator() {
			//    var i = 0;
			//    return {
			//        next: function() {
			//            if (i >= np) {
			//                return {done: true};
			//            }
			//            var x = i % nx;
			//            var y = Math.floor(i / nx);
			//            var λ = λ0 + x * Δλ;
			//            var φ = φ0 + y * Δφ;
			//            return {value: [λ, φ, i++], done: false};
			//        },
			//    };
			//}

			/**
			 * @param {Function} cb the callback ƒ(λ, φ, i), where a truthy return value terminates the iteration.
			 * @param {number?} start the starting grid index.
			 * @returns {number} the grid index of the next iteration, or NaN if iteration is finished.
			 */
			function forEach(cb, start) {
				for(var i = start || 0; i < np; i++) {
					var x = i % nx;
					var y = Math.floor(i / nx);
					var λ = λ0 + x * Δλ;
					var φ = φ0 + y * Δφ;
					if(cb(λ, φ, i)) {
						return i + 1; // Terminate iteration and return next grid index.
					}
				}
				return NaN; // Iteration is finished.
			}

			/**
			 * @param {number[]} coord [λ, φ] in degrees
			 * @returns {number} index of closest grid point or NaN if further than Δλ/2 or Δφ/2 from the grid boundary.
			 */
			function closest(coord) {
				var λ = coord[0];
				var φ = coord[1];
				if(λ === λ && φ === φ) {
					var x = µ.floorMod(λ - λ0, 360) / Δλ;
					var y = (φ - φ0) / Δφ;
					var rx = Math.round(x);
					var ry = Math.round(y);

					if(0 <= ry && ry < ny && 0 <= rx && (rx < nx || rx === nx && isCylinder)) {
						var i = ry * nx + rx;
						return rx === nx ? i - nx : i;
					}
				}
				return NaN;
			}

			/**
			 * Identifies the four points surrounding the specified coordinates. The result is a six-element array:
			 *
			 *     0-3: the indices of the four points, in increasing order.
			 *     4,5: the fraction that λ,φ is away from the first point, normalized to the range [0, 1].
			 *
			 * Example:
			 * <pre>
			 *          1      2           After converting λ and φ to positions on the x and y grid axes, we find the
			 *         fx  x   cx          four points that enclose point [x, y]. These points are at the four
			 *          | =1.4 |           corners specified by the floor and ceiling of x and y. For example, given
			 *       --i00-|--i10-- fy 8   x = 1.4 and y = 8.3, the four surrounding grid points are [1, 8], [2, 8],
			 *     y ___|_ .   |           [1, 9] and [2, 9]. These points have index i00, i10, i01, i11, respectively,
			 *   =8.3   |      |           and result of this function is an array [i00, i10, i01, i11, 0.4, 0.3].
			 *       --i01----i11-- cy 9
			 *          |      |
			 * </pre>
			 *
			 * @param {number[]} coord [λ, φ] in degrees
			 * @returns {number[]} the indices of the four grid points surrounding the coordinate pair and the (x,y) fraction,
			 *          or [NaN, NaN, NaN, NaN, NaN, NaN] if all points are not found.
			 */
			function closest4(coord) {
				var λ = coord[0];
				var φ = coord[1];
				if(λ === λ && φ === φ) {
					var x = µ.floorMod(λ - λ0, 360) / Δλ;
					var y = (φ - φ0) / Δφ;
					var fx = Math.floor(x);
					var fy = Math.floor(y);
					var cx = fx + 1;
					var cy = fy + 1;
					var Δx = x - fx;
					var Δy = y - fy;

					if(0 <= fy && cy < ny && 0 <= fx && (cx < nx || cx === nx && isCylinder)) {
						var i00 = fy * nx + fx;
						var i10 = i00 + 1;
						var i01 = i00 + nx;
						var i11 = i01 + 1;
						if(cx === nx) {
							i10 -= nx;
							i11 -= nx;
						}
						return [i00, i10, i01, i11, Δx, Δy];
					}
				}
				return [NaN, NaN, NaN, NaN, NaN, NaN];
			}

			return {
				forEach: forEach,
				closest: closest,
				closest4: closest4,
			}
		};

	}, {
		"../micro": 39
	}],
	33: [function(require, module, exports) {
		/*
		 * bilinear: a bilinear interpolator for scalar and vector fields that also handles triangles (3 points).
		 */
		"use strict";

		/**
		 * @param grid a grid that supports the "closest4" function.
		 * @param {Float32Array|number[]} data backing data, the same length as the grid.
		 * @returns {Function} a bilinear interpolation function f([λ, φ]) -> v
		 */
		function scalar(grid, data) {

			/**
			 * @param {number[]} coord [λ, φ] in degrees.
			 * @returns {number} the bilinear interpolated value or NaN if none.
			 */
			function bilinear(coord) {
				var indices = grid.closest4(coord);

				var i00 = indices[0];
				if(i00 === i00) {
					var i10 = indices[1];
					var i01 = indices[2];
					var i11 = indices[3];
					var x = indices[4];
					var y = indices[5];
					var rx = 1 - x;
					var ry = 1 - y;

					var v00 = data[i00];
					var v10 = data[i10];
					var v01 = data[i01];
					var v11 = data[i11];

					if(v00 === v00) {
						if(v10 === v10 && v01 === v01 && v11 === v11) {
							var a = rx * ry,
								b = x * ry,
								c = rx * y,
								d = x * y;
							return v00 * a + v10 * b + v01 * c + v11 * d; // 4 points found.

						} else if(v11 === v11 && v10 === v10 && x >= y) {
							return v10 + rx * (v00 - v10) + y * (v11 - v10); // 3 points found, triangle interpolate.

						} else if(v01 === v01 && v11 === v11 && x < y) {
							return v01 + x * (v11 - v01) + ry * (v00 - v01); // 3 points found, triangle interpolate.

						} else if(v01 === v01 && v10 === v10 && x <= ry) {
							return v00 + x * (v10 - v00) + y * (v01 - v00); // 3 points found, triangle interpolate.
						}
					} else if(v11 === v11 && v01 === v01 && v10 === v10 && x > ry) {
						return v11 + rx * (v01 - v11) + ry * (v10 - v11); // 3 points found, triangle interpolate.
					}
				}
				return NaN;
			}

			return bilinear;
		}

		/**
		 * @param grid a grid that supports the "closest4" function.
		 * @param {Float32Array|number[]} uData backing u data, the same length as the grid.
		 * @param {Float32Array|number[]} vData backing v data, the same length as the grid.
		 * @returns {Function} a bilinear interpolation function f([λ, φ]) -> [u, v, m]
		 */
		function vector(grid, uData, vData) {

			function triangleInterpolateVector(x, y, u0, v0, u1, v1, u2, v2) {
				var u = u0 + x * (u2 - u0) + y * (u1 - u0);
				var v = v0 + x * (v2 - v0) + y * (v1 - v0);
				return [u, v, Math.sqrt(u * u + v * v)];
			}

			/**
			 * @param {number[]} coord [λ, φ] in degrees.
			 * @returns {number[]} the bilinear interpolated value as a vector [u, v, m] or [NaN, NaN, NaN] if none.
			 */
			function bilinear(coord) {
				var indices = grid.closest4(coord);

				var i00 = indices[0];
				if(i00 === i00) {
					var i10 = indices[1];
					var i01 = indices[2];
					var i11 = indices[3];
					var x = indices[4];
					var y = indices[5];
					var rx = 1 - x;
					var ry = 1 - y;

					var u00 = uData[i00];
					var u10 = uData[i10];
					var u01 = uData[i01];
					var u11 = uData[i11];

					var v00 = vData[i00];
					var v10 = vData[i10];
					var v01 = vData[i01];
					var v11 = vData[i11];

					if(v00 === v00) {
						if(v10 === v10 && v01 === v01 && v11 === v11) {
							var a = rx * ry,
								b = x * ry,
								c = rx * y,
								d = x * y;
							var u = u00 * a + u10 * b + u01 * c + u11 * d;
							var v = v00 * a + v10 * b + v01 * c + v11 * d;
							return [u, v, Math.sqrt(u * u + v * v)]; // 4 points found.

						} else if(v11 === v11 && v10 === v10 && x >= y) {
							return triangleInterpolateVector(rx, y, u10, v10, u11, v11, u00, v00);

						} else if(v01 === v01 && v11 === v11 && x < y) {
							return triangleInterpolateVector(x, ry, u01, v01, u00, v00, u11, v11);

						} else if(v01 === v01 && v10 === v10 && x <= ry) {
							return triangleInterpolateVector(x, y, u00, v00, u01, v01, u10, v10);
						}
					} else if(v11 === v11 && v01 === v01 && v10 === v10 && x > ry) {
						return triangleInterpolateVector(rx, ry, u11, v11, u10, v10, u01, v01);
					}
				}
				return [NaN, NaN, NaN];
			}

			return bilinear;
		}

		module.exports = {
			scalar: scalar,
			vector: vector,
		};

	}, {}],
	34: [function(require, module, exports) {
		/*
		 * nearest: a nearest-neighbor interpolator for scalar and vector fields.
		 */
		"use strict";

		/**
		 * @param grid a grid that supports the "closest" function.
		 * @param {Float32Array|number[]} data backing data, the same length as the grid.
		 * @returns {Function} a nearest neighbor interpolation function f([λ, φ]) -> v
		 */
		function scalar(grid, data) {

			/**
			 * @param {number[]} coord [λ, φ] in degrees.
			 * @returns {number} the nearest neighbor value or NaN if none.
			 */
			function nearest(coord) {
				var i = grid.closest(coord);
				return i === i ? data[i] : NaN;
			}

			return nearest;
		}

		/**
		 * @param grid a grid that supports the "closest" function.
		 * @param {Float32Array|number[]} uData backing u data, the same length as the grid.
		 * @param {Float32Array|number[]} vData backing v data, the same length as the grid.
		 * @returns {Function} a nearest neighbor interpolation function f([λ, φ]) -> [u, v, m]
		 */
		function vector(grid, uData, vData) {

			/**
			 * @param {number[]} coord [λ, φ] in degrees.
			 * @returns {number[]} the nearest neighbor value as a vector [u, v, m] or [NaN, NaN, NaN] if none.
			 */
			function nearest(coord) {
				var i = grid.closest(coord);
				if(i === i) {
					var u = uData[i],
						v = vData[i];
					return [u, v, Math.sqrt(u * u + v * v)];
				} else {
					return [NaN, NaN, NaN];
				}
			}

			return nearest;
		}

		module.exports = {
			scalar: scalar,
			vector: vector,
		};

	}, {}],
	35: [function(require, module, exports) {
		"use strict";

		/**
		 * @param {string} type event type
		 * @param {string} key the key property
		 * @param {number} which the browser-specific numeric key code
		 * @param {string} str the string representation of the numeric key code
		 * @returns {string} the normalized 'key' value.
		 */
		function normalize(type, key, which, str) {
			if(key) {
				switch(key) {
					case "Esc":
						return "Escape";
				}
				return key; // Use the key property if it exists.
			}
			switch(which) {
				case 27:
					return "Escape";
			}
			if(type === "keypress" && str) {
				return str; // The string representation is acceptable for keypress events. For keyup and keydown, it is not.
			}
			return "NYI";
		}

		module.exports = {

			/**
			 * @param {KeyboardEvent} event the keyboard keyup, keypress, or keydown event.
			 * @return {string} the 'key' property of the event (or equivalent if not yet supported by the browser), or "NYI"
			 *         if I haven't bothered to investigate cross browser behavior for the specific key.
			 */
			key: function(event) {
				var type = event.type,
					key = event.key,
					which = +event.which,
					str = String.fromCharCode(which);
				//console.log("type", type, "key", key, "which", which, "str", str, " => ", normalize(type, key, which, str));
				return normalize(type, key, which, str);
			}
		};

	}, {}],
	36: [function(require, module, exports) {
		"use strict";

		var lang = module.exports = {};

		lang.preferred = function(navigator) {
			return navigator.languages || [navigator.language || navigator.userLanguage || "en"];
		};

		/**
		 * Parses an IETF language tag into pieces.
		 *
		 * {language: "zh", script: "Hans", region: "CN"}
		 * {language: "en", script: "", region: "US"}
		 * {language: "en", script: "", region: ""}
		 * {language: "", script: "", region: ""}
		 *
		 * @param {string} tag
		 */
		lang.parse = function(tag) {
			var result = {
					language: "",
					script: "",
					region: ""
				},
				parts = (tag || "").toLowerCase().split("-");
			for(var i = 0; i < parts.length; i++) {
				var part = parts[i];
				if(part.length === 1) { // stop parsing when we hit an extension
					break;
				}
				if(i === 0) {
					result.language = part;
				} else if(/^[a-z]{4}$/.test(part)) {
					result.script = part.substring(0, 1).toUpperCase() + part.substring(1);
				} else if(/^([a-z]{2}|\d{3})$/.test(part)) {
					result.region = part.toUpperCase();
				}
			}
			return result;
		};

		/**
		 * Returns the most appropriate match for the specified language tag, or "" if none can be found.
		 *
		 * @param struct a parsed IETF language tag
		 * @returns {string}
		 */
		lang.match = function(struct) {
			// $LANG$
			switch(struct.language) {
				case "en":
					return "en";
				case "cs":
					return "cs";
				case "fr":
					return "fr";
				case "ja":
					return "ja";
				case "pt":
					return "pt";
				case "ru":
					return "ru";
				case "zh":
					switch(struct.script) {
						case "Hant":
							return "zh-TW";
						case "Hans":
							return "zh-CN";
					}
					switch(struct.region) {
						case "HK":
						case "MO":
						case "TW":
							return "zh-TW";
						case "SG":
						case "CN":
							return "zh-CN";
					}
			}
			return "";
		};

		lang.best = function(navigator) {
			var preferred = lang.preferred(navigator);
			for(var i = 0; i < preferred.length; i++) {
				var parsed = lang.parse(preferred[i]);
				var matched = lang.match(parsed);
				if(matched) return matched;
			}
			return "";
		};

	}, {}],
	37: [function(require, module, exports) {
		(function(global) {
			"use strict";

			/**
			 * @param {Object?} console the object to handle log operations [default: global console]
			 * @returns {{debug: *, info: *, warn: *, error: *, time: *, timeEnd: *}} a log object
			 */
			module.exports = function(console) {

				function extract(ref, f) {
					return typeof f === "function" ? f.bind(ref) : function() {};
				}

				console = console || global.console || {};

				return {
					debug: extract(console, console.log),
					info: extract(console, console.info),
					warn: extract(console, console.warn),
					error: extract(console, console.error),
					time: extract(console, console.time),
					timeEnd: extract(console, console.timeEnd),
				};
			};

		}).call(this, typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
	}, {}],
	38: [function(require, module, exports) {
		(function(global) {
			! function() {
				"use strict";

				//require("./clock").calibration({server: "/"}); /*calibration({fixed: "2015-11-24T00:00Z"});*/

				var _ = require("underscore");
				var d3 = require("d3");
				require("./d3-geo-projection")(d3);
				require("./d3-geo-polyhedron")(d3);
				var µ = require("./micro");
				var blacklist = require("./blacklist");

				var earth = global.earth = require("./earth");
				global.main = function(bridge) {
					bridge = bridge || {};
					var log = require("./log")(bridge.console);
					var app = {
						log: log,
						bridge: bridge
					};
					if(µ.isDevMode()) {
						log.debug("dev mode enabled");
					}
					_.defer(earth, app); // defer all side effects
				};

				if(µ.isEmbeddedInIFrame() && _.isFunction(window.ga)) {
					window.ga("send", "event", "iframe", document.referrer.split("/")[2] || document.referrer);
				}

				if(µ.isEmbeddedInIFrame() && blacklist.contains(document.referrer)) {
					return blacklist.deny();
				} else if(µ.isAppMode()) {
					d3.select("#details").classed("invisible", true);
				} else {
					//global.main(require("./bridge"));
					global.main();
				}
			}();

		}).call(this, typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
	}, {
		"./blacklist": 24,
		"./d3-geo-polyhedron": 26,
		"./d3-geo-projection": 27,
		"./earth": 29,
		"./log": 37,
		"./micro": 39,
		"d3": 3,
		"underscore": 5
	}],
	39: [function(require, module, exports) {
		/**
		 * micro - a grab bag of somewhat useful utility functions and other stuff that requires unit testing
		 *
		 * Copyright (c) 2016 Cameron Beccario
		 *
		 * For a free version of this project, see https://github.com/cambecc/earth
		 */

		! function() {
			"use strict";

			var µ = module.exports = {};

			var d3 = require("d3");
			var _ = require("underscore");
			var Backbone = require("backbone");
			var when = require("when");
			var decoder = require("./decoder");
			var utc = require("./utc");

			var π = Math.PI,
				τ = 2 * π,
				/*DEG = 360 / τ,*/ RAD = τ / 360;
			var H = 0.0000360; // 0.0000360°φ ~= 4m
			var DEFAULT_CONFIG = "current/wind/surface/level/orthographic";

			function topologyFile() {
				return µ.isMobile() ? "/data/earth-topo-mobile.json?v3" : "/data/earth-topo.json?v3";
			}

			µ.isDevMode = function() {
				return +window.location.port === 8081;
			};

			µ.siteLangCode = function() {
				return d3.select("html").attr("lang") || "en";
			};

			µ.siteInstance = function() {
				var match = window.location.hostname.match(/(.*)\.nullschool\.net$/) || [],
					name = match[1] || "earth";
				return name === "earth" ? "" : name;
			};

			/**
			 * @returns {boolean} true if the specified value is truthy.
			 */
			µ.isTruthy = function(x) {
				return !!x;
			};

			/**
			 * @returns {boolean} true if the specified value is not null and not undefined.
			 */
			µ.isValue = function(x) {
				return x !== null && x !== undefined;
			};

			/**
			 * @returns {Object} the first argument if not null and not undefined, otherwise the second argument.
			 */
			µ.coalesce = function(a, b) {
				return µ.isValue(a) ? a : b;
			};

			/**
			 * Converts the argument to a number, including special cases for fractions:
			 *     0.25  -> 0.25
			 *     "1/4" -> 0.25
			 *     [1,4] -> 0.25
			 *     ".25" -> 0.25
			 *
			 * @param x any object. When an array, then interpreted as the fraction: a[0] / a[1]. When a string containing
			 *        a slash, the the value is first converted to an array by splitting on "/".
			 * @returns {number} the specified argument converted to a number.
			 */
			µ.decimalize = function(x) {
				if(_.isString(x) && x.indexOf("/") >= 0) {
					x = x.split("/");
				}
				return Array.isArray(x) && x.length === 2 ? (x[0] / x[1]) : +x;
			};

			/**
			 * @param {Array|*} x the value to convert to a scalar.
			 * @returns {*} the magnitude if x is a vector (i.e., x[2]), otherwise x itself.
			 */
			µ.scalarize = function(x) {
				return Array.isArray(x) ? x[2] : x;
			};

			/**
			 * @returns {number} returns remainder of floored division, i.e., floor(a / n). Useful for consistent modulo
			 *          of negative numbers. See http://en.wikipedia.org/wiki/Modulo_operation.
			 */
			µ.floorMod = function(a, n) {
				var f = a - n * Math.floor(a / n);
				// When a is within an ulp of n, f can be equal to n (because the subtraction has no effect). But the result
				// should be in the range [0, n), so check for this case. Example: floorMod(-1e-16, 10)
				return f === n ? 0 : f;
			};

			/**
			 * @returns {number} distance between two points having the form [x, y].
			 */
			µ.distance = function(a, b) {
				var Δx = b[0] - a[0];
				var Δy = b[1] - a[1];
				return Math.sqrt(Δx * Δx + Δy * Δy);
			};

			/**
			 * @returns {number} the value x clamped to the range [low, high].
			 */
			µ.clamp = function(x, low, high) {
				return Math.max(low, Math.min(x, high));
			};

			/**
			 * @returns {number} the fraction of the bounds [low, high] covered by the value x, after clamping x to the
			 *          bounds. For example, given bounds=[10, 20], this method returns 1 for x>=20, 0.5 for x=15 and 0
			 *          for x<=10.
			 */
			µ.proportion = function(x, low, high) {
				return(µ.clamp(x, low, high) - low) / (high - low);
			};

			/**
			 * @returns {number} the value p within the range [0, 1], scaled to the range [low, high].
			 */
			µ.spread = function(p, low, high) {
				return p * (high - low) + low;
			};

			/**
			 * @returns {string} the specified string with the first letter capitalized.
			 */
			µ.capitalize = function(s) {
				return s.length === 0 ? s : s.charAt(0).toUpperCase() + s.substr(1);
			};

			/**
			 * @returns {boolean} true if agent is probably firefox. Don't really care if this is accurate.
			 */
			µ.isFF = function() {
				return(/firefox/i).test(navigator.userAgent);
			};

			/**
			 * @returns {boolean} true if agent is probably a mobile device. Don't really care if this is accurate.
			 */
			µ.isMobile = function() {
				return(/android|blackberry|iemobile|ipad|iphone|ipod|opera mini|webos/i).test(navigator.userAgent);
			};

			µ.isEmbeddedInIFrame = function() {
				return self != top;
			};

			/**
			 * Finds the method having the specified name on the object thisArg, and returns it as a function bound
			 * to thisArg. If no method can be found, or thisArg is not a value, then returns null.
			 *
			 * @param thisArg the object
			 * @param methodName the method name to bind to the object
			 * @returns {Function} the method bound to the object, if it exists.
			 */
			µ.bindup = function(thisArg, methodName) {
				return µ.isValue(thisArg) && thisArg[methodName] ? thisArg[methodName].bind(thisArg) : null;
			};

			/**
			 * @returns {{width: number, height: number}} an object that describes the size of the browser's current view.
			 */
			µ.view = function() {
				var w = window;
				var d = document && document.documentElement;
				var b = document && document.getElementsByTagName("body")[0];
				var x = w.innerWidth || d.clientWidth || b.clientWidth;
				var y = w.innerHeight || d.clientHeight || b.clientHeight;
				return {
					width: x,
					height: y
				};
			};

			/**
			 * Removes all children of the specified DOM element.
			 */
			µ.removeChildren = function(element) {
				while(element.firstChild) {
					element.removeChild(element.firstChild);
				}
			};

			/**
			 * @returns {*} a new mouse click event instance
			 */
			µ.newClickEvent = function() {
				try {
					return new MouseEvent("click", {
						view: window,
						bubbles: true,
						cancelable: true
					});
				} catch(e) {
					// Chrome mobile supports only the old fashioned, deprecated way of constructing events. :(
					var event = document.createEvent("MouseEvents");
					event.initMouseEvent("click", true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
					return event;
				}
			};

			/**
			 * @returns {Object} clears and returns the specified Canvas element's 2d context.
			 */
			µ.clearCanvas = function(canvas) {
				canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height);
				return canvas;
			};

			function colorInterpolator(start, end) {
				var r = start[0],
					g = start[1],
					b = start[2];
				var Δr = end[0] - r,
					Δg = end[1] - g,
					Δb = end[2] - b;
				return function(i, a) {
					return [Math.floor(r + i * Δr), Math.floor(g + i * Δg), Math.floor(b + i * Δb), a];
				};
			}

			/**
			 * Produces a color style in a rainbow-like trefoil color space. Not quite HSV, but produces a nice
			 * spectrum. See http://krazydad.com/tutorials/makecolors.php.
			 *
			 * @param hue the hue rotation in the range [0, 1]
			 * @param a the alpha value in the range [0, 255]
			 * @returns {Array} [r, g, b, a]
			 */
			µ.sinebowColor = function(hue, a) {
				// Map hue [0, 1] to radians [0, 5/6τ]. Don't allow a full rotation because that keeps hue == 0 and
				// hue == 1 from mapping to the same color.
				var rad = hue * τ * 5 / 6;
				rad *= 0.75; // increase frequency to 2/3 cycle per rad

				var s = Math.sin(rad);
				var c = Math.cos(rad);
				var r = Math.floor(Math.max(0, -c) * 255);
				var g = Math.floor(Math.max(s, 0) * 255);
				var b = Math.floor(Math.max(c, 0, -s) * 255);
				return [r, g, b, a];
			};

			var BOUNDARY = 0.45;
			var fadeToWhite = colorInterpolator(µ.sinebowColor(1.0, 0), [255, 255, 255]);

			/**
			 * Interpolates a sinebow color where 0 <= i <= j, then fades to white where j < i <= 1.
			 *
			 * @param i number in the range [0, 1]
			 * @param a alpha value in range [0, 255]
			 * @returns {Array} [r, g, b, a]
			 */
			µ.extendedSinebowColor = function(i, a) {
				return i <= BOUNDARY ?
					µ.sinebowColor(i / BOUNDARY, a) :
					fadeToWhite((i - BOUNDARY) / (1 - BOUNDARY), a);
			};

			function asColorStyle(r, g, b, a) {
				return "rgba(" + r + ", " + g + ", " + b + ", " + a + ")";
			}

			/**
			 * @returns {Array} of wind colors and a method, indexFor, that maps wind magnitude to an index on the color scale.
			 */
			µ.windIntensityColorScale = function(step, maxWind) {
				var result = [];
				for(var j = 85; j <= 255; j += step) {
					result.push(asColorStyle(j, j, j, 1.0));
				}
				result.indexFor = function(m) { // map wind speed to a style
					return Math.floor(Math.min(m, maxWind) / maxWind * (result.length - 1));
				};
				return result;
			};

			/**
			 * Creates a color scale composed of the specified segments. Segments is an array of two-element arrays of the
			 * form [value, color], where value is the point along the scale and color is the [r, g, b] color at that point.
			 * For example, the following creates a scale that smoothly transitions from red to green to blue along the
			 * points 0.5, 1.0, and 3.5:
			 *
			 *     [ [ 0.5, [255, 0, 0] ],
			 *       [ 1.0, [0, 255, 0] ],
			 *       [ 3.5, [0, 0, 255] ] ]
			 *
			 * @param segments array of color segments
			 * @returns {Function} a function(point, alpha) that returns the color [r, g, b, alpha] for the given point.
			 */
			µ.segmentedColorScale = function(segments) {
				var points = [],
					interpolators = [],
					ranges = [];
				for(var i = 0; i < segments.length - 1; i++) {
					points.push(segments[i + 1][0]);
					interpolators.push(colorInterpolator(segments[i][1], segments[i + 1][1]));
					ranges.push([segments[i][0], segments[i + 1][0]]);
				}

				return function(point, alpha) {
					var i;
					for(i = 0; i < points.length - 1; i++) {
						if(point <= points[i]) {
							break;
						}
					}
					var range = ranges[i];
					return interpolators[i](µ.proportion(point, range[0], range[1]), alpha);
				};
			};

			/**
			 * Returns a human readable string for the provided coordinates.
			 */
			µ.formatCoordinates = function(λ, φ) {
				return Math.abs(φ).toFixed(2) + "° " + (φ >= 0 ? "N" : "S") + ", " +
					Math.abs(λ).toFixed(2) + "° " + (λ >= 0 ? "E" : "W");
			};

			/**
			 * Returns a human readable string for the provided scalar in the given units.
			 */
			µ.formatScalar = function(value, units) {
				return units.conversion(value).toFixed(units.precision);
			};

			/**
			 * Returns a human readable string for the provided rectangular wind vector in the given units.
			 * See http://mst.nerc.ac.uk/wind_vect_convs.html.
			 */
			µ.formatVector = function(wind, units) {
				console.log(wind);
				console.log(units);
				var d = Math.atan2(-wind[0], -wind[1]) / τ * 360; // calculate into-the-wind cardinal degrees
				var wd = Math.round((d + 360) % 360 / 5) * 5; // shift [-180, 180] to [0, 360], and round to nearest 5.
				return wd.toFixed(0) + "° @ " + µ.formatScalar(wind[2], units);
			};

			function xhrResolver(resource, d, error, result) {
				return error ?
					!error.status ?
					d.reject({
						status: -1,
						message: "Cannot load resource: " + resource + ": " + error,
						resource: resource,
						error: error
					}) :
					d.reject({
						status: error.status,
						message: error.statusText,
						resource: resource,
						error: error
					}) :
					d.resolve(result);
			}

			/**
			 * Returns a promise for a JSON resource (URL) fetched via XHR. If the load fails, the promise rejects with an
			 * object describing the reason: {status: http-status-code, message: http-status-text, resource:}.
			 */
			µ.loadJson = function(resource) {
				var d = when.defer();
				d3.json(resource, xhrResolver.bind(null, resource, d));
				return d.promise;
			};

			/**
			 * Same as loadJson but returns a singleton promise for each URL.
			 */
			µ.loadJsonOnce = _.memoize(µ.loadJson);

			/**
			 * Returns a promise for an EPAK resource (URL) fetched via XHR. If the load fails, the promise rejects
			 * with an object describing the reason: {status: http-status-code, message: http-status-text, resource:}.
			 */
			µ.loadEpak = function(resource) {
				var d = when.defer();
				d3.xhr(resource)
					.responseType("arraybuffer")
					.response(function(req) {
						return decoder.decodeEpak(req.response);
					}) // UNDONE: promise swallows decoding exceptions
					.get(xhrResolver.bind(null, resource, d));
				return d.promise;
			};

			/**
			 * Returns the distortion introduced by the specified projection at the given point.
			 *
			 * This method uses finite difference estimates to calculate warping by adding a very small amount (h) to
			 * both the longitude and latitude to create two lines. These lines are then projected to pixel space, where
			 * they become diagonals of triangles that represent how much the projection warps longitude and latitude at
			 * that location.
			 *
			 * <pre>
			 *        (λ, φ+h)                  (xλ, yλ)
			 *           .                         .
			 *           |               ==>        \
			 *           |                           \   __. (xφ, yφ)
			 *    (λ, φ) .____. (λ+h, φ)       (x, y) .--
			 * </pre>
			 *
			 * See:
			 *     Map Projections: A Working Manual, Snyder, John P: pubs.er.usgs.gov/publication/pp1395
			 *     gis.stackexchange.com/questions/5068/how-to-create-an-accurate-tissot-indicatrix
			 *     www.jasondavies.com/maps/tissot
			 *
			 * @returns {Array} array of scaled derivatives [dx/dλ, dy/dλ, dx/dφ, dy/dφ]
			 */
			µ.distortion = function(projection, λ, φ, x, y) {
				var hλ = λ < 0 ? H : -H;
				var hφ = φ < 0 ? H : -H;
				var pλ = projection([λ + hλ, φ]);
				var pφ = projection([λ, φ + hφ]);

				// Meridian scale factor (see Snyder, equation 4-3), where R = 1. This handles issue where length of 1° λ
				// changes depending on φ. Without this, there is a pinching effect at the poles.
				var k = Math.cos(φ * RAD);
				var hλk = hλ * k;

				return [
					(pλ[0] - x) / hλk, // dx/dλ
					(pλ[1] - y) / hλk, // dy/dλ
					(pφ[0] - x) / hφ, // dx/dφ
					(pφ[1] - y) / hφ // dy/dφ
				];
			};

			/**
			 * Returns a new agent. An agent executes tasks and stores the result of the most recently completed task.
			 *
			 * A task is a value or promise, or a function that returns a value or promise. After submitting a task to
			 * an agent using the submit() method, the task is evaluated and its result becomes the agent's value,
			 * replacing the previous value. If a task is submitted to an agent while an earlier task is still in
			 * progress, the earlier task is cancelled and its result ignored. Evaluation of a task may even be skipped
			 * entirely if cancellation occurs early enough.
			 *
			 * Agents are Backbone.js Event emitters. When a submitted task is accepted for invocation by an agent, a
			 * "submit" event is emitted. This event has the agent as its sole argument. When a task finishes and
			 * the agent's value changes, an "update" event is emitted, providing (value, agent) as arguments. If a task
			 * fails by either throwing an exception or rejecting a promise, a "reject" event having arguments (err, agent)
			 * is emitted. If an event handler throws an error, a "fail" event having arguments (err, agent) is emitted.
			 *
			 * The current task can be cancelled by invoking the agent.cancel() method, and the cancel status is available
			 * as the Boolean agent.cancel.requested key. Within the task callback, the "this" context is set to the agent,
			 * so a task can know to abort execution by checking the this.cancel.requested key. Similarly, a task can cancel
			 * itself by invoking this.cancel().
			 *
			 * Example pseudocode:
			 * <pre>
			 *     var agent = newAgent();
			 *     agent.on("update", function(value) {
			 *         console.log("task completed: " + value);  // same as agent.value()
			 *     });
			 *
			 *     function someLongAsynchronousProcess(x) {  // x === "abc"
			 *         var d = when.defer();
			 *         // some long process that eventually calls: d.resolve(result)
			 *         return d.promise;
			 *     }
			 *
			 *     agent.submit(someLongAsynchronousProcess, "abc");
			 * </pre>
			 *
			 * @param {string} [name]
			 * @returns {Object}
			 */
			µ.newAgent = function(name) {

				/**
				 * @returns {Function} a cancel function for a task.
				 */
				function cancelFactory() {
					return function cancel() {
						cancel.requested = true;
						return agent;
					};
				}

				/**
				 * Invokes the specified task.
				 * @param cancel the task's cancel function.
				 * @param taskAndArguments the [task-function-or-value, arg0, arg1, ...] array.
				 */
				function runTask(cancel, taskAndArguments) {

					function run(args) {
						return cancel.requested ? null : _.isFunction(task) ? task.apply(agent, args) : task;
					}

					function accept(result) {
						if(!cancel.requested) {
							value = result;
							agent.trigger("update", result, agent);
						}
					}

					function reject(err) {
						if(!cancel.requested) { // ANNOYANCE: when cancelled, this task's error is silently suppressed
							agent.trigger("reject", err, agent);
						}
					}

					function fail(err) {
						agent.trigger("fail", err, agent);
					}

					try {
						// When all arguments are resolved, invoke the task then either accept or reject the result.
						var task = taskAndArguments[0];
						when.all(_.rest(taskAndArguments)).then(run).then(accept, reject).catch(fail).done();
						agent.trigger("submit", agent);
					} catch(err) {
						fail(err);
					}
				}

				var value = undefined;
				var runTask_debounced = _.debounce(runTask, 0); // ignore multiple simultaneous submissions--reduces noise
				var agent = {

					/**
					 * @returns {Object} this agent's current value.
					 */
					value: function() {
						return value;
					},

					/**
					 * Cancels this agent's most recently submitted task.
					 */
					cancel: cancelFactory(),

					/**
					 * Submit a new task and arguments to invoke the task with. The task may return a promise for
					 * asynchronous tasks, and all arguments may be either values or promises. The previously submitted
					 * task, if any, is immediately cancelled.
					 * @returns {agent} the agent.
					 */
					submit: function(task, arg0, arg1, and_so_on) {
						// immediately cancel the previous task
						this.cancel();
						// schedule the new task and update the agent with its associated cancel function
						runTask_debounced(this.cancel = cancelFactory(), arguments);
						return this;
					},

					toString: function() {
						return name;
					},
				};

				return _.extend(agent, Backbone.Events);
			};

			/**
			 * Parses a URL hash fragment:
			 *
			 * example: "2013/11/14/0900Z/wind/isobaric/1000hPa/orthographic=26.50,-153.00,1430/overlay=off"
			 * output: {date: "2013/11/14", hour: "0900", param: "wind", surface: "isobaric", level: "1000hPa",
			 *          projection: "orthographic", orientation: "26.50,-153.00,1430", overlayType: "off"}
			 *
			 * grammar:
			 *     hash   := ( "current" | yyyy / mm / dd / hhhh "Z" ) / param / surface / level [ / option [ / option ... ] ]
			 *     option := type [ "=" number [ "," number [ ... ] ] ]
			 *
			 * @param hash the hash fragment.
			 * @param projectionNames the set of allowed projections.
			 * @param overlayTypes the set of allowed overlays.
			 * @param oldAttr the old set of attributes that this parse will replace.
			 * @returns {Object} the result of the parse.
			 */
			µ.parse = function(hash, projectionNames, overlayTypes, oldAttr) {
				var option, result = {};
				//             1        2        3          4          5            6      7      8    9
				var tokens = /^(current|(\d{4})\/(\d{1,2})\/(\d{1,2})\/(\d{3,4})Z)\/(\w+)\/(\w+)\/(\w+)([\/].+)?/.exec(hash);
				if(tokens) {
					var date = tokens[1] === "current" ? "current" : {
						year: +tokens[2],
						month: +tokens[3],
						day: +tokens[4]
					};
					if(date !== "current") {
						var hour = (tokens[5].length === 3 ? "0" : "") + tokens[5];
						date.hour = +hour.substr(0, 2);
						date.minute = +hour.substr(2);
					}
					result = {
						date: date, // "current" or {year:, month:, day:, hour:, minute:}
						param: tokens[6], // non-empty alphanumeric _
						surface: tokens[7], // non-empty alphanumeric _
						level: tokens[8], // non-empty alphanumeric _
						projection: "orthographic",
						orientation: "",
						topology: topologyFile(),
						overlayType: "default",
						showGridPoints: false,
						animate: true,
						loc: null, // location mark coordinates [λ, φ]
						argoFloat: null,

						// These fields are part of the app state but not saved in the URL because they are transient state.
						// CONSIDER: separate state into three types of persistence: world, user, and session.
						//           corresponds to: URL, LocalStorage, document/javascript heap.

						hd: oldAttr.hd || false, // high def mode. retain current setting because hd is not in the hash.
					};
					µ.coalesce(tokens[9], "").split("/").forEach(function(segment) {
						if((option = /^(\w+)(=([\d\-.,]*))?$/.exec(segment))) {
							if(_.has(projectionNames, option[1])) {
								result.projection = option[1]; // non-empty alphanumeric _
								result.orientation = µ.coalesce(option[3], ""); // comma delimited string of numbers, or ""
							} else if(option[1] === "loc") {
								var parts = _.isString(option[3]) ? option[3].split(",") : [];
								var λ = +parts[0],
									φ = +parts[1];
								if(λ === λ && φ === φ) {
									result.loc = [λ, φ];
								}
							}
						} else if((option = /^overlay=(\w+)$/.exec(segment))) {
							if(overlayTypes.has(option[1]) || option[1] === "default") {
								result.overlayType = option[1];
							}
						} else if((option = /^grid=(\w+)$/.exec(segment))) {
							if(option[1] === "on") {
								result.showGridPoints = true;
							}
						} else if((option = /^anim=(\w+)$/.exec(segment))) {
							if(option[1] === "off") {
								result.animate = false;
							}
						} else if((option = /^argo=(\w+)$/.exec(segment))) {
							switch(option[1]) {
								case "planned":
								case "recent":
								case "active":
								case "dead":
									result.argoFloat = option[1];
							}
						}
					});
				}
				return result;
			};

			/**
			 * A Backbone.js Model that persists its attributes as a human readable URL hash fragment. Loading from and
			 * storing to the hash fragment is handled by the sync method.
			 */
			var Configuration = Backbone.Model.extend({
				id: 0,
				_ignoreNextHashChangeEvent: false,
				_projectionNames: null,
				_overlayTypes: null,

				/**
				 * @returns {String} this configuration converted to a hash fragment.
				 */
				toHash: function() {
					var attr = this.attributes;
					var dir = attr.date === "current" ? "current" : utc.print(attr.date, "{yyyy}/{MM}/{dd}/{hh}{mm}Z");
					var proj = [attr.projection, attr.orientation].filter(µ.isTruthy).join("=");
					var loc = attr.loc ? "loc=" + attr.loc.map(function(e) {
						return e.toFixed(3);
					}).join(",") : "";
					var ol = !µ.isValue(attr.overlayType) || attr.overlayType === "default" ? "" : "overlay=" + attr.overlayType;
					var grid = attr.showGridPoints ? "grid=on" : "";
					var anim = attr.animate ? "" : "anim=off";
					var argoFloat = attr.argoFloat ? "argo=" + attr.argoFloat : "";
					// node: HD not serialized because we want to reduce traffic when sharing links.
					return [dir, attr.param, attr.surface, attr.level, anim, ol, grid, argoFloat, proj, loc].filter(µ.isTruthy).join("/");
				},

				/**
				 * Synchronizes between the configuration model and the hash fragment in the URL bar. Invocations
				 * caused by "hashchange" events must have the {trigger: "hashchange"} option specified.
				 */
				sync: function(method, model, options) {
					switch(method) {
						case "read":
							if(options.trigger === "hashchange" && model._ignoreNextHashChangeEvent) {
								model._ignoreNextHashChangeEvent = false;
								return;
							}
							model.set(µ.parse(
								window.location.hash.substr(1) || DEFAULT_CONFIG,
								model._projectionNames,
								model._overlayTypes,
								model.attributes));
							break;
						case "update":
						case "create":
							var nextHash = "#" + model.toHash();
							// Some state, like HD mode, is not persisted in the hash so the hash may not change.
							if(window.location.hash !== nextHash) {
								// Ugh. Setting the hash fires a hashchange event during the next event loop turn. Ignore it.
								model._ignoreNextHashChangeEvent = true;
								window.location.hash = nextHash;
							}
							break;
					}
				}
			});

			/**
			 * A Backbone.js Model to hold the page's configuration as a set of attributes: date, layer, projection,
			 * orientation, etc. Changes to the configuration fire events which the page's components react to. For
			 * example, configuration.save({projection: "orthographic"}) fires an event which causes the globe to be
			 * re-rendered with an orthographic projection.
			 *
			 * All configuration attributes are persisted in a human readable form to the page's hash fragment (and
			 * vice versa). This allows deep linking and back-button navigation.
			 *
			 * @returns {Configuration} Model to represent the hash fragment, using the specified set of allowed projections.
			 */
			µ.buildConfiguration = function(projectionNames, overlayTypes) {
				var result = new Configuration();
				result._projectionNames = projectionNames;
				result._overlayTypes = overlayTypes;
				return result;
			};

			/**
			 * @param query URL search query string, e.g., "?a=1&b=2&c=&d"
			 * @returns {Object} an object of terms, e.g., {a: "1", b: "2", c: "", d: null}
			 */
			µ.parseQueryString = function(query) {
				return _.object(query.split(/[?&]/).filter(µ.isTruthy).map(function(term) {
					return term.split("=").map(decodeURIComponent).concat([null]); // use null for 2nd element when undefined
				}));
			};

			µ.dateToConfig = function(date) {
				return {
					date: utc.normalize(date)
				};
			};

			µ.isAppMode = function() {
				return _.has(µ.parseQueryString(window.location.search), "app");
			};

			µ.isKioskMode = function() {
				return _.has(µ.parseQueryString(window.location.search), "kiosk");
			};

		}();

	}, {
		"./decoder": 28,
		"./utc": 61,
		"backbone": 1,
		"d3": 3,
		"underscore": 5,
		"when": 23
	}],
	40: [function(require, module, exports) {
		"use strict";
		module.exports = function() {

			var _ = require("underscore");
			var µ = require("../micro");
			var palette = require("./palette");

			var bounds = [0, 5000]; // units: J/kg

			// Diverging 11-class RdBu, from colorbrewer2.org:
			var g = µ.segmentedColorScale([
				[0, [5, 48, 97]], // weak
				[500, [33, 102, 172]], // weak
				[1000, [67, 147, 195]], // weak
				[1500, [146, 197, 222]], // moderate
				[2000, [209, 229, 240]], // moderate
				[2500, [247, 247, 247]], // moderate
				[3000, [253, 219, 199]], // strong
				[3500, [244, 165, 130]], // strong
				[4000, [214, 96, 77]], // strong
				[4500, [178, 24, 43]], // extreme
				[5000, [103, 0, 31]] // extreme
			]);

			var array = _.range(1000).map(function() {
				return null;
			}); // create null-filled array.
			palette.fillRange(array, bounds, bounds, g);
			var gradient = palette.gradient(array, bounds);

			return {
				bounds: bounds,
				gradient: gradient
			};

		}();

	}, {
		"../micro": 39,
		"./palette": 46,
		"underscore": 5
	}],
	41: [function(require, module, exports) {
		"use strict";
		module.exports = function() {

			var palette = require("./palette");

			var bounds = [0.0003578, 0.0003770]; // units: CO2 Bulk Mixing Ratio (Column Mass/ps) -- or CO2 column load??
			var stops = [{
				color: [0, 0, 0],
				mode: "lch",
				p: bounds[0]
			}, {
				color: [100, 0, 0],
				mode: "lch",
				p: 0.0003650
			}, {
				color: [164, 36, 0],
				mode: "lch",
				p: 0.0003675
			}, {
				color: [255, 220, 140],
				mode: "lch",
				p: 0.0003710
			}, {
				color: [255, 255, 255],
				mode: "lch",
				p: bounds[1]
			}];

			var scales = palette.scalesFrom(stops);
			scales = palette.smooth(scales, 2, [0.0003700, 0.0003720]);
			var gradient = palette.quantize(scales, bounds, 1000);

			return {
				bounds: bounds,
				gradient: gradient
			};

		}();

	}, {
		"./palette": 46
	}],
	42: [function(require, module, exports) {
		"use strict";
		module.exports = function(Δ) {

			var palette = require("./palette");

			var bounds = [320 + Δ, 430 + Δ]; // units: CO2 Surface Concentration ppmv
			var stops = [{
				color: [0, 0, 0],
				mode: "lch",
				p: bounds[0]
			}, {
				color: [100, 0, 0],
				mode: "lch",
				p: 360 + Δ
			}, {
				color: [164, 36, 0],
				mode: "lch",
				p: 365 + Δ
			}, {
				color: [255, 220, 140],
				mode: "lch",
				p: 380 + Δ
			}, {
				color: [255, 255, 255],
				mode: "lch",
				p: 410 + Δ
			}, {
				color: [0, 210, 255],
				mode: "lch",
				p: bounds[1]
			}];

			var scales = palette.scalesFrom(stops);
			var x = stops[3].p,
				y = stops[4].p,
				z = stops[1].p;
			scales = palette.smooth(scales, 0, [z - 2, z + 2]);
			scales = palette.smooth(scales, 3, [x - 2, x + 2]);
			scales = palette.smooth(scales, 5, [y - 2, y + 2]);
			var gradient = palette.quantize(scales, bounds, 1000);

			return {
				bounds: bounds,
				gradient: gradient
			};

		};

	}, {
		"./palette": 46
	}],
	43: [function(require, module, exports) {
		"use strict";
		module.exports = function() {

			var palette = require("./palette");

			var bounds = [40, 2500]; // units: ppb
			var stops = [{
					color: [0, 38, 40],
					mode: "lab",
					p: bounds[0]
				}, {
					color: [255, 255, 224],
					mode: "lch",
					p: 400
				}, // background is ~100 ppb https://en.wikipedia.org/wiki/MOPITT
				{
					color: [0, 0, 154],
					mode: "lab",
					p: 1800
				}, {
					color: [0, 0, 0],
					mode: "lab",
					p: bounds[1]
				}
			];

			var scales = palette.scalesFrom(stops);
			scales = palette.smooth(scales, 0, [360, 440]);
			scales = palette.smooth(scales, 2, [1700, 1900]);
			var gradient = palette.quantize(scales, bounds, 2000);

			return {
				bounds: bounds,
				gradient: gradient
			};

		}();

	}, {
		"./palette": 46
	}],
	44: [function(require, module, exports) {
		"use strict";
		module.exports = function() {

			var µ = require("../micro");
			var palette = require("./palette");
			var kindlmann = require("./kindlmann");

			var bounds = [0.0001, 3.0000],
				logBounds = bounds.map(Math.log); // units: τ
			var gradient = palette.gradient(kindlmann, logBounds);

			return {
				bounds: bounds,
				gradient: function(v, a) {
					return gradient(Math.log(v), a);
				},
				spread: function(p) {
					return Math.exp(µ.spread(p, logBounds[0], logBounds[1]));
				},
			};

		}();

	}, {
		"../micro": 39,
		"./kindlmann": 45,
		"./palette": 46
	}],
	45: [function(require, module, exports) {
		"use strict";

		/**
		 * Kindlmann Linear Luminance palette
		 *
		 * Kindlmann, G. Reinhard, E. and Creem, S., 2002, Face-based Luminance Matching for Perceptual Colormap Generation,
		 *     IEEE – Proceedings of the conference on Visualization ’02
		 *
		 * Prepared by Matteo Niccoli:
		 *     https://mycarta.wordpress.com/2012/12/06/the-rainbow-is-deadlong-live-the-rainbow-part-5-cie-lab-linear-l-rainbow/
		 */
		module.exports = [
			[4, 4, 4],
			[10, 3, 8],
			[13, 4, 11],
			[16, 5, 14],
			[18, 5, 16],
			[21, 6, 18],
			[22, 7, 19],
			[24, 8, 21],
			[26, 8, 22],
			[27, 9, 24],
			[28, 10, 25],
			[30, 11, 26],
			[31, 12, 27],
			[32, 12, 28],
			[33, 13, 29],
			[35, 14, 31],
			[36, 14, 32],
			[37, 15, 32],
			[38, 15, 33],
			[39, 16, 34],
			[40, 17, 35],
			[41, 17, 36],
			[42, 18, 38],
			[43, 19, 38],
			[44, 19, 39],
			[46, 20, 41],
			[46, 20, 45],
			[46, 21, 50],
			[45, 21, 55],
			[45, 21, 60],
			[45, 22, 64],
			[45, 23, 67],
			[45, 23, 71],
			[45, 24, 75],
			[45, 24, 77],
			[45, 25, 81],
			[45, 25, 84],
			[44, 26, 87],
			[44, 27, 90],
			[45, 27, 92],
			[45, 28, 95],
			[44, 29, 98],
			[44, 29, 100],
			[44, 30, 103],
			[44, 31, 106],
			[44, 31, 109],
			[44, 32, 110],
			[44, 33, 113],
			[44, 34, 116],
			[43, 34, 118],
			[42, 35, 121],
			[40, 38, 120],
			[38, 40, 119],
			[36, 42, 120],
			[34, 44, 120],
			[33, 46, 120],
			[32, 47, 120],
			[31, 49, 121],
			[30, 50, 122],
			[30, 51, 123],
			[29, 52, 123],
			[29, 53, 125],
			[28, 55, 125],
			[28, 56, 126],
			[27, 57, 127],
			[28, 58, 128],
			[28, 59, 129],
			[27, 60, 129],
			[27, 61, 131],
			[27, 62, 132],
			[27, 63, 133],
			[28, 64, 134],
			[27, 65, 135],
			[27, 66, 136],
			[27, 68, 137],
			[27, 69, 138],
			[25, 71, 136],
			[22, 73, 134],
			[21, 74, 133],
			[20, 76, 131],
			[17, 78, 129],
			[16, 79, 128],
			[15, 81, 126],
			[14, 82, 125],
			[10, 84, 123],
			[10, 85, 122],
			[9, 87, 120],
			[8, 88, 119],
			[7, 89, 118],
			[6, 91, 117],
			[4, 92, 115],
			[4, 94, 114],
			[4, 95, 114],
			[3, 96, 112],
			[1, 98, 111],
			[1, 99, 110],
			[0, 100, 109],
			[0, 101, 108],
			[0, 103, 107],
			[0, 104, 106],
			[0, 105, 105],
			[0, 107, 104],
			[0, 108, 101],
			[0, 110, 100],
			[0, 111, 99],
			[0, 112, 98],
			[0, 114, 96],
			[0, 115, 95],
			[0, 116, 93],
			[0, 118, 92],
			[0, 119, 90],
			[0, 120, 89],
			[0, 121, 88],
			[0, 123, 86],
			[0, 124, 85],
			[0, 125, 83],
			[0, 127, 82],
			[0, 128, 80],
			[0, 129, 79],
			[0, 131, 77],
			[0, 132, 75],
			[0, 133, 73],
			[0, 134, 72],
			[0, 136, 70],
			[0, 137, 68],
			[0, 138, 66],
			[0, 139, 65],
			[0, 141, 64],
			[0, 142, 63],
			[0, 143, 61],
			[0, 145, 60],
			[0, 146, 60],
			[0, 147, 58],
			[0, 149, 57],
			[0, 150, 56],
			[0, 151, 55],
			[0, 153, 53],
			[0, 154, 52],
			[0, 155, 51],
			[0, 157, 50],
			[0, 158, 48],
			[0, 159, 47],
			[0, 160, 45],
			[0, 162, 44],
			[0, 163, 42],
			[0, 164, 41],
			[0, 165, 39],
			[0, 167, 36],
			[0, 168, 34],
			[0, 169, 31],
			[0, 170, 23],
			[0, 169, 8],
			[9, 170, 0],
			[20, 171, 0],
			[29, 172, 0],
			[35, 173, 0],
			[40, 174, 0],
			[45, 175, 0],
			[48, 176, 0],
			[52, 177, 0],
			[55, 178, 0],
			[59, 179, 0],
			[61, 180, 0],
			[64, 181, 0],
			[66, 182, 0],
			[68, 183, 0],
			[71, 184, 0],
			[73, 185, 0],
			[76, 186, 0],
			[78, 187, 0],
			[79, 188, 0],
			[81, 189, 0],
			[83, 190, 0],
			[85, 191, 0],
			[87, 192, 0],
			[92, 193, 0],
			[99, 193, 0],
			[106, 193, 0],
			[114, 193, 0],
			[119, 194, 0],
			[125, 194, 0],
			[130, 194, 0],
			[135, 195, 0],
			[140, 195, 0],
			[145, 195, 0],
			[149, 196, 0],
			[153, 196, 0],
			[157, 197, 0],
			[161, 197, 0],
			[165, 197, 0],
			[169, 198, 0],
			[172, 198, 0],
			[176, 199, 0],
			[180, 199, 0],
			[184, 199, 0],
			[186, 200, 0],
			[190, 201, 0],
			[193, 201, 0],
			[197, 201, 0],
			[200, 202, 0],
			[201, 201, 24],
			[203, 202, 51],
			[206, 202, 65],
			[207, 203, 77],
			[209, 203, 87],
			[212, 203, 95],
			[213, 204, 103],
			[215, 205, 109],
			[218, 205, 116],
			[219, 206, 121],
			[221, 207, 127],
			[223, 207, 132],
			[226, 207, 138],
			[227, 208, 143],
			[229, 209, 147],
			[231, 209, 151],
			[232, 210, 155],
			[235, 211, 159],
			[237, 211, 164],
			[238, 212, 168],
			[240, 212, 172],
			[243, 213, 175],
			[243, 214, 179],
			[245, 214, 183],
			[248, 215, 186],
			[248, 216, 189],
			[248, 218, 193],
			[247, 219, 195],
			[247, 220, 198],
			[247, 222, 201],
			[248, 223, 204],
			[247, 224, 206],
			[247, 226, 209],
			[247, 227, 211],
			[247, 229, 214],
			[247, 230, 216],
			[247, 231, 218],
			[247, 232, 220],
			[248, 234, 224],
			[247, 235, 225],
			[247, 236, 229],
			[247, 238, 231],
			[247, 239, 232],
			[248, 240, 235],
			[248, 242, 237],
			[247, 243, 239],
			[248, 244, 241],
			[248, 246, 244],
			[248, 247, 246],
			[248, 248, 248],
			[249, 249, 249],
			[251, 251, 251],
			[252, 252, 252],
			[253, 253, 253],
			[254, 254, 254],
			[255, 255, 255]
		];

	}, {}],
	46: [function(require, module, exports) {
		"use strict";
		! function() {

			var palette = module.exports = {};

			var _ = require("underscore");
			var chroma = require("chroma-js");

			/**
			 * Converts an array of N color stops into N-1 chroma scales:
			 *
			 *    {color: "black", mode: "lch", p: 10},
			 *    {color: "grey",  mode: "lab", p: 20},
			 *    {color: "white",              p: 30}
			 *
			 * Result:
			 *    scale 0 [black, grey] over domain [10, 20] in lch color space
			 *    scale 1 [grey, white] over domain [20, 30] in lab color space
			 *
			 * @param {Array} stops an array of colors stops to convert into scales.
			 * @returns {Array} an array of chroma.js scales.
			 */
			palette.scalesFrom = function(stops) {
				var scales = [];
				for(var i = 0; i < stops.length - 1; i++) {
					var lo = stops[i],
						hi = stops[i + 1];
					scales.push(chroma.scale([chroma(lo.color), chroma(hi.color)]).domain([lo.p, hi.p]).mode(lo.mode));
				}
				return scales;
			};

			/**
			 * Given a range [a, b] and two adjoining color scales L (i) and R (i+1) sharing domain point p, insert a new
			 * bezier-interpolated scale M over the points [a, p, b].
			 *
			 * @param {Array} scales
			 * @param {Number} i
			 * @param {Array} range
			 * @returns {Array}
			 */
			palette.smooth = function(scales, i, range) {

				//         p                  p
				//        / \              M _-_
				//       /   \      =>      /   \
				//      /     \            a     b
				//   L /       \ R      K /       \ S
				//    Lx       Ry        Lx       Ry

				var L = scales[i],
					R = scales[i + 1],
					lx = L.domain()[0],
					ry = R.domain()[1],
					p = L.domain()[1];
				var a = range[0],
					b = range[1];

				var K = chroma.scale([L(lx), L(a)]).domain([lx, a]).mode(L.mode());
				var M = chroma.scale(chroma.bezier([L(a), L(p), R(b)])).domain([a, b]).mode(L.mode());
				var S = chroma.scale([R(b), R(ry)]).domain([b, ry]).mode(R.mode());

				return [].concat(scales.slice(0, i), [K, M, S], scales.slice(i + 2));
			};

			/**
			 * Use array A of length n to define a linear scale over domain [x, y] such that [x, y] is mapped onto indices
			 * [0, n-1]. The range [a, b] is then mapped to indices [i, j] using this scale, and the elements A[i] to A[j] are
			 * filled with the results of f(v) where v iterates over [a, b].
			 *
			 * @param {Array} array the destination array to fill.
			 * @param {Array} domain the values [x, y], inclusive.
			 * @param {Array} range the values [a, b], inclusive.
			 * @param {Function} f the value function f(v).
			 */
			palette.fillRange = function(array, domain, range, f) {

				//    |-----------domain------------|
				//    |        |---range---|        |
				//    x        a           b        y
				//    0        i           j      len-1
				// A [0, ..., f(a), ..., f(b), ..., 0]

				var x = domain[0],
					y = domain[1],
					Δ = (y - x) / (array.length - 1);
				var a = range[0],
					b = range[1];
				var start = Math.round((a - x) / Δ),
					end = Math.round((b - x) / Δ);
				for(var i = start; i < end + 1; i++) {
					array[i] = f(x + i * Δ);
				}
			};

			/**
			 * Use array A of length n to define a linear scale over domain [x, y] such that [x, y] is mapped onto indices
			 * [0, n-1]. The result is a function f(v) that returns A[i] where v is clamped to [x, y] and mapped to i.
			 *
			 * @param array the array A.
			 * @param domain the values [x, y], inclusive.
			 * @returns {Function} f(v) that returns A[v->i].
			 */
			palette.gradient = function(array, domain) {
				var x = domain[0],
					y = domain[1],
					Δ = (y - x) / (array.length - 1);
				return function(v, a) {
					var c = array[Math.round((Math.max(x, Math.min(v, y)) - x) / Δ)];
					c[3] = a; // UNDONE: gradient function should not pass in alpha
					return c;
				};
			};

			/**
			 * Convert a set of chroma.js scales into an accessor function over a computed array of rgba colors.
			 *
			 * @param {Array} scales the set of scales.
			 * @param {Array} domain the values [x, y], inclusive.
			 * @param {Number} resolution the number of elements of the computed color array.
			 * @returns {Function} the gradient function f(v) over the resulting color array.
			 */
			palette.quantize = function(scales, domain, resolution) {
				var array = _.range(resolution).map(function() {
					return null;
				}); // create null-filled array.
				scales.forEach(function(scale) {
					palette.fillRange(array, domain, scale.domain(), function(v) {
						return scale(v).rgba().map(Math.round);
					});
				});
				return palette.gradient(array, domain);
			};

		}();

	}, {
		"chroma-js": 2,
		"underscore": 5
	}],
	47: [function(require, module, exports) {
		"use strict";
		module.exports = function() {

			var palette = require("./palette");
			var chroma = require("chroma-js");

			var bounds = [0, 150]; // units: kg/m3 == mm

			var stops = [{
				color: chroma("#719aa9").darker(30),
				mode: "lch",
				p: bounds[0]
			}, {
				color: "aliceblue",
				mode: "lch",
				p: 2
			}, {
				color: chroma("navy").brighter(10),
				mode: "lch",
				p: 15
			}, {
				color: "gold",
				mode: "lch",
				p: 100
			}, {
				color: "white",
				mode: "lch",
				p: bounds[1]
			}];

			var scales = palette.scalesFrom(stops);
			scales = palette.smooth(scales, 0, [1, 3]);
			scales = palette.smooth(scales, 2, [13, 17]);
			var gradient = palette.quantize(scales, bounds, 2000);

			return {
				bounds: bounds,
				gradient: gradient
			};

		}();

		/*
		https://en.wikipedia.org/wiki/Rain#Intensity

		Light rain — when the precipitation rate is < 2.5 mm (0.098 in) per hour
		Moderate rain — when the precipitation rate is between 2.5 mm (0.098 in) - 7.6 mm (0.30 in) or 10 mm (0.39 in) per hour
		Heavy rain — when the precipitation rate is > 7.6 mm (0.30 in) per hour, or between 10 mm (0.39 in) and 50 mm (2.0 in) per hour
		Violent rain — when the precipitation rate is > 50 mm (2.0 in) per hour
		*/
	}, {
		"./palette": 46,
		"chroma-js": 2
	}],
	48: [function(require, module, exports) {
		"use strict";
		module.exports = function() {

			var µ = require("../micro");
			var palette = require("./palette");

			//      v*1e-14
			//             1
			//         3,000  boat trail start
			//        20,000
			//       115,000  boat trail peak
			//       262,000     1 ppb
			//    19,650,000    75 ppb, EPA 1hr std limit
			//    65,500,000   250 ppb, Amer Conf of Gvt Indus Hygen short term exposure limit
			//   163,226,000   623 ppb, largest seen in 2 weeks of geos-driver
			//   484,962,000  1851 ppb, top of log scale
			// 1,310,000,000  5000 ppb, OSHA PEL

			var bounds = [10e-14, 88800000e-14],
				logBounds = bounds.map(Math.log); // units: kg/m3
			var stops = [{
				color: [134, 134, 107],
				mode: "lch",
				p: logBounds[0]
			}, {
				color: [144, 144, 117],
				mode: "lch",
				p: Math.log(100e-14)
			}, {
				color: [255, 255, 224],
				mode: "lch",
				p: Math.log(7000e-14)
			}, {
				color: [0, 0, 128],
				mode: "lab",
				p: Math.log(19000000e-14)
			}, {
				color: [23, 20, 18],
				mode: "lab",
				p: logBounds[1]
			}];

			var scales = palette.scalesFrom(stops);
			scales = palette.smooth(scales, 1, [Math.log(5000e-14), Math.log(9000e-14)]);
			scales = palette.smooth(scales, 3, [Math.log(10000000e-14), Math.log(30000000e-14)]);
			var gradient = palette.quantize(scales, logBounds, 400);

			return {
				bounds: bounds,
				gradient: function(v, a) {
					return gradient(Math.log(v), a);
				},
				spread: function(p) {
					return Math.exp(µ.spread(p, logBounds[0], logBounds[1]));
				},
			};

		}();

	}, {
		"../micro": 39,
		"./palette": 46
	}],
	49: [function(require, module, exports) {
		"use strict";
		module.exports = function() {

			var µ = require("../micro");
			var palette = require("./palette");
			var chroma = require("chroma-js");

			var bounds = [0.002, 2.500],
				logBounds = bounds.map(Math.log); // units: τ
			var scale = chroma.scale(chroma.cubehelix()).domain(logBounds);
			var gradient = palette.quantize([scale], logBounds, 500);

			return {
				bounds: bounds,
				gradient: function(v, a) {
					return gradient(Math.log(v), a);
				},
				spread: function(p) {
					return Math.exp(µ.spread(p, logBounds[0], logBounds[1]));
				},
			};

		}();

	}, {
		"../micro": 39,
		"./palette": 46,
		"chroma-js": 2
	}],
	50: [function(require, module, exports) {
		"use strict";
		module.exports = function() {

			var _ = require("underscore");
			var µ = require("../micro");
			var palette = require("./palette");

			var g = µ.segmentedColorScale([
				[193, [37, 4, 42]],
				[206, [41, 10, 130]],
				[219, [81, 40, 40]],
				[233.15, [192, 37, 149]], // -40 C/F
				[255.372, [70, 215, 215]], // 0 F
				[273.15, [21, 84, 187]], // 0 C
				[275.15, [24, 132, 14]], // just above 0 C
				[291, [247, 251, 59]],
				[298, [235, 167, 21]],
				[311, [230, 71, 39]],
				[328, [88, 27, 67]]
			]);

			var bounds = [193, 328]; // units: m/s

			var array = _.range(2000).map(function() {
				return null;
			}); // create null-filled array.
			palette.fillRange(array, bounds, bounds, g);
			var gradient = palette.gradient(array, bounds);

			return {
				bounds: bounds,
				gradient: gradient
			};

		}();

	}, {
		"../micro": 39,
		"./palette": 46,
		"underscore": 5
	}],
	51: [function(require, module, exports) {
		"use strict";
		module.exports = function() {

			var _ = require("underscore");
			var µ = require("../micro");
			var palette = require("./palette");

			var bounds = [0, 100]; // units: m/s

			var array = _.range(1000).map(function() {
				return null;
			}); // create null-filled array.
			palette.fillRange(array, [0, 1], [0, 1], µ.extendedSinebowColor);
			var gradient = palette.gradient(array, bounds);

			return {
				bounds: bounds,
				gradient: gradient
			};

		}();

	}, {
		"../micro": 39,
		"./palette": 46,
		"underscore": 5
	}],
	52: [function(require, module, exports) {
		"use strict";

		var utc = require("../utc");
		var rectangularGrid = require("../grid/rectangular");
		var nearest = require("../interpolate/nearest");
		var bilinear = require("../interpolate/bilinear");
		var util = require("./util");
		var strings = require("./strings");

		module.exports = function(file) {
			if(Array.isArray(file)) {
				file = util.munge(file, ["u", "v"], ["time", "level", "lat", "lon"]);
			}
			var epak = file,
				header = epak.header,
				vars = header.variables;
			var u = vars["u"] || vars["u-component_of_wind_isobaric"] || vars["u-component_of_wind_height_above_ground"];
			var v = vars["v"] || vars["v-component_of_wind_isobaric"] || vars["v-component_of_wind_height_above_ground"];

			// dims are: time,level,lat,lon
			var time = vars[u.dimensions[0]];
			var lat = vars[u.dimensions[2]];
			var lon = vars[u.dimensions[3]];
			var uData = epak.blocks[u.data.block];
			var vData = epak.blocks[v.data.block];

			var grid = rectangularGrid(lon.sequence, lat.sequence);
			var defaultInterpolator = bilinear.vector(grid, uData, vData);

			return {
				sourceHTML: strings.gfs,
				date: function() {
					return utc.parts(time.data[0]);
				},
				grid: function() {
					return grid;
				},
				field: function() {
					return {
						valueAt: function(i) {
							var u = uData[i];
							var v = vData[i];
							return [u, v, Math.sqrt(u * u + v * v)];
						},
						nearest: nearest.vector(grid, uData, vData),
						bilinear: bilinear.vector(grid, uData, vData),
					}
				},
				interpolate: function(coord) {
					return defaultInterpolator(coord);
				},
			};
		};

	}, {
		"../grid/rectangular": 32,
		"../interpolate/bilinear": 33,
		"../interpolate/nearest": 34,
		"../utc": 61,
		"./strings": 56,
		"./util": 57
	}],
	53: [function(require, module, exports) {
		"use strict";

		var _ = require("underscore");
		var windFactory = require("./gfs-wind");
		var scalarProduct = require("./scalarProduct");
		var strings = require("./strings");

		module.exports = function(windFile, airdensFile) {

			var windGrid = windFactory(windFile);
			var airdensGrid = scalarProduct(airdensFile, /air_density/, strings.gfs, ["air_density"]);
			var windValueAt = windGrid.field().valueAt;
			var windNearest = windGrid.field().nearest;
			var windBilinear = windGrid.field().bilinear;
			var airdensValueAt = airdensGrid.field().valueAt;
			var airdensNearest = airdensGrid.field().nearest;
			var airdensBilinear = airdensGrid.field().bilinear;

			function wpd(wind, ρ) {
				var m = wind[2];
				return 0.5 * ρ * m * m * m;
			}

			function valueAt(i) {
				return wpd(windValueAt(i), airdensValueAt(i));
			}

			function nearest(coord) {
				return wpd(windNearest(coord), airdensNearest(coord));
			}

			function bilinear(coord) {
				return wpd(windBilinear(coord), airdensBilinear(coord));
			}

			return _.extend(airdensGrid, {
				field: function() {
					return {
						valueAt: valueAt,
						nearest: nearest,
						bilinear: bilinear,
					}
				},
				interpolate: function(coord) {
					return bilinear(coord);
				},
			});
		};

	}, {
		"./gfs-wind": 52,
		"./scalarProduct": 55,
		"./strings": 56,
		"underscore": 5
	}],
	54: [function(require, module, exports) {
		"use strict";

		var utc = require("../utc");
		var rectangularGrid = require("../grid/rectangular");
		var nearest = require("../interpolate/nearest");
		var bilinear = require("../interpolate/bilinear");

		var strings = require("./strings");

		module.exports = function(file) {
			var epak = file,
				header = epak.header,
				vars = header.variables;
			var u = vars["u"];
			var v = vars["v"];

			// dims are: time,depth,lat,lon
			var time = vars[u.dimensions[0]];
			var lat = vars[u.dimensions[2]];
			var lon = vars[u.dimensions[3]];
			var uData = epak.blocks[u.data.block];
			var vData = epak.blocks[v.data.block];

			var grid = rectangularGrid(lon.sequence, lat.sequence);
			var defaultInterpolator = bilinear.vector(grid, uData, vData);

			return {
				sourceHTML: strings.oscar,
				date: function() {
					return utc.parts(time.data[0]);
				},
				grid: function() {
					return grid;
				},
				field: function() {
					return {
						valueAt: function(i) {
							var u = uData[i];
							var v = vData[i];
							return [u, v, Math.sqrt(u * u + v * v)];
						},
						nearest: nearest.vector(grid, uData, vData),
						bilinear: bilinear.vector(grid, uData, vData),
					}
				},
				interpolate: function(coord) {
					return defaultInterpolator(coord);
				},
			};
		};

	}, {
		"../grid/rectangular": 32,
		"../interpolate/bilinear": 33,
		"../interpolate/nearest": 34,
		"../utc": 61,
		"./strings": 56
	}],
	55: [function(require, module, exports) {
		"use strict";

		var _ = require("underscore");
		var util = require("./util");
		var utc = require("../utc");
		var rectangularGrid = require("../grid/rectangular");
		var nearest = require("../interpolate/nearest");
		var bilinear = require("../interpolate/bilinear");

		module.exports = function(bundle, selector, centerName, keys, transform) {
			// Assumes dimension structure of: [time, ..., lat, lon]
			if(Array.isArray(bundle)) {
				bundle = util.munge(bundle, keys);
			}
			if(!bundle.blocks) {
				bundle = util.blockify(bundle, selector);
			}
			var epak = bundle,
				header = epak.header,
				vars = header.variables;
			var x = _.find(_.keys(vars), function(e) {
				return selector.test(e);
			});
			var target = vars[x];
			var dims = target.dimensions;
			var time = vars[dims[0]];
			var lat = vars[_.last(dims, 2)[0]];
			var lon = vars[_.last(dims, 2)[1]];
			var data = epak.blocks[target.data.block];

			if(_.isFunction(transform)) {
				transform(data);
			}

			var grid = rectangularGrid(lon.sequence, lat.sequence);
			var interpolators = {
				valueAt: function(i) {
					return data[i];
				},
				nearest: nearest.scalar(grid, data),
				bilinear: bilinear.scalar(grid, data),
			};

			return {
				sourceHTML: centerName,
				date: function() {
					return utc.parts(time.data[0]);
				},
				grid: function() {
					return grid;
				},
				field: function() {
					return interpolators;
				},
				interpolate: function(coord) {
					return interpolators.bilinear(coord);
				},
			};
		};

	}, {
		"../grid/rectangular": 32,
		"../interpolate/bilinear": 33,
		"../interpolate/nearest": 34,
		"../utc": 61,
		"./util": 57,
		"underscore": 5
	}],
	56: [function(require, module, exports) {
		"use strict";

		var µ = require("../micro");

		var co2Link = µ.isKioskMode() ? "" : " [<a href='about.html#co2' class='internal-link'>important note</a>]";

		module.exports = {
			geos: "GEOS-5 / GMAO / NASA",
			geosCO2: "GEOS-5 / GMAO / NASA" + co2Link,
			gfs: "GFS / NCEP / US National Weather Service",
			oscar: "OSCAR / Earth & Space Research",
			rtgsst: "RTG-SST / NCEP / US National Weather Service",
			ww3: "WAVEWATCH III / NCEP / NWS",
		};

	}, {
		"../micro": 39
	}],
	57: [function(require, module, exports) {
		"use strict";

		var me = module.exports = {};

		var _ = require("underscore");
		var utc = require("../utc");

		/**
		 * Makes an old-style grib2json file look like an epak file
		 *
		 * @param {Array} records the grib2json payload.
		 * @param {string[]} varNames the variable names associated with the grib2json records.
		 * @param {string[]?} dimensions the dimensions to inject the header, or ["time", "lat", "lon"] by default.
		 * @returns {Object} an epak-like structure.
		 */
		me.munge = function(records, varNames, dimensions) {

			var header = records[0].header;
			var validTime = utc.add(utc.parts(header.refTime), {
				hour: header.forecastTime
			});
			var variables = {
				time: {
					data: [utc.printISO(validTime)]
				},
				lat: {
					sequence: {
						start: header.la1,
						delta: -header.dy,
						size: header.ny
					}
				},
				lon: {
					sequence: {
						start: header.lo1,
						delta: header.dx,
						size: header.nx
					}
				},
			};
			var blocks = [];

			varNames.forEach(function(key, i) {
				variables[key] = {
					dimensions: dimensions || ["time", "lat", "lon"],
					data: {
						block: i
					}
				};
				blocks[i] = records[i].data;
			});

			return {
				header: {
					variables: variables
				},
				blocks: blocks,
			};

		};

		/**
		 * Some early epaks did not use binary blocks, so convert them into the expected form.
		 */
		me.blockify = function(epak, selector) {
			var variables = epak.variables;
			var blocks = [];

			_.keys(variables).forEach(function(key) {
				if(selector.test(key)) {
					var v = variables[key];
					blocks.push(v.data);
					v.data = {
						block: blocks.length - 1
					};
				}
			});

			return {
				header: epak,
				blocks: blocks
			};
		};

	}, {
		"../utc": 61,
		"underscore": 5
	}],
	58: [function(require, module, exports) {
		"use strict";

		var π = Math.PI,
			τ = 2 * π,
			RAD = τ / 360;

		var utc = require("../utc");
		var rectangularGrid = require("../grid/rectangular");
		var nearest = require("../interpolate/nearest");
		var bilinear = require("../interpolate/bilinear");
		var strings = require("./strings");

		module.exports = function(file) {
			var epak = file,
				header = epak.header,
				vars = header.variables;
			var direction = vars["Primary_wave_direction_surface"];
			var period = vars["Primary_wave_mean_period_surface"];

			// dims are: time,lat,lon
			var time = vars[direction.dimensions[0]];
			var lat = vars[direction.dimensions[1]];
			var lon = vars[direction.dimensions[2]];
			var dirData = epak.blocks[direction.data.block];
			var perData = epak.blocks[period.data.block];
			var uData = new Float32Array(dirData.length);
			var vData = new Float32Array(dirData.length);

			for(var i = 0; i < dirData.length; i++) {
				var φ = dirData[i] * RAD; // wave direction in radians
				var m = perData[i]; // wave period (treated as velocity)
				if(φ === φ) {
					uData[i] = -m * Math.sin(φ);
					vData[i] = -m * Math.cos(φ);
				} else {
					uData[i] = vData[i] = NaN;
				}
			}

			var grid = rectangularGrid(lon.sequence, lat.sequence);
			var defaultInterpolator = bilinear.vector(grid, uData, vData);

			return {
				sourceHTML: strings.ww3,
				date: function() {
					return utc.parts(time.data[0]);
				},
				grid: function() {
					return grid;
				},
				field: function() {
					return {
						valueAt: function(i) {
							var u = uData[i];
							var v = vData[i];
							return [u, v, Math.sqrt(u * u + v * v)];
						},
						nearest: nearest.vector(grid, uData, vData),
						bilinear: bilinear.vector(grid, uData, vData),
					}
				},
				interpolate: function(coord) {
					return defaultInterpolator(coord);
				},
			};
		};

	}, {
		"../grid/rectangular": 32,
		"../interpolate/bilinear": 33,
		"../interpolate/nearest": 34,
		"../utc": 61,
		"./strings": 56
	}],
	59: [function(require, module, exports) {
		"use strict";

		/**
		 * products - defines the behavior of weather data grids, including grid construction, interpolation, and color scales.
		 *
		 * Copyright (c) 2016 Cameron Beccario
		 *
		 * For a free version of this project, see https://github.com/cambecc/earth
		 */
		module.exports = function(app) {

			var products = {};

			var d3 = require("d3");
			var when = require("when");
			var _ = require("underscore");
			var µ = require("./micro");
			var utc = require("./utc");
			var clock = require("./clock");
			var scalarProduct = require("./product/scalarProduct");
			var strings = require("./product/strings");

			var CUTOFF = new Date("2016-02-20");

			function gaia(path) {
				var host = µ.isDevMode() ? "" : "https://gaia.nullschool.net",
					url = host + path,
					instance = µ.siteInstance();
				return instance ? url + "?" + instance : url;
			}

			function old(path) {
				return "https://earth.nullschool.net" + path;
			}
			products.gaia = gaia;

			var geosUrl = function(path, date) {
				return date < CUTOFF ? old("/data/geos/" + path) : gaia("/data/geos/" + path);
			};
			var gfsUrl = function(path, date) {
				return date < CUTOFF ? old("/data/weather/" + path) : gaia("/data/gfs/" + path);
			};
			var oscarUrl = function(path) {
				return gaia("/data/oscar/" + path);
			};
			var rtgsstUrl = function(path, date) {
				return date < CUTOFF ? old("/data/ocean/" + path) : gaia("/data/rtgsst/" + path);
			};
			var ww3Url = function(path, date) {
				return date < CUTOFF ? old("/data/ocean/" + path) : gaia("/data/ww3/" + path);
			};
			var argoUrl = function(path) {
				return gaia("/data/argo/" + path);
			};

			var aotLink = µ.isKioskMode() ? "" : "(<a href='about.html#aot' class='internal-link'>AOT</a>)";

			function buildProduct(overrides) {
				return _.extend({
					descriptionHTML: "",
					paths: [],
					/** @returns {Object} the product's date parts. */
					date: function() {
						return null;
					},
					/**
					 * @param {number} step the number of steps away: ±1 or ±10
					 * @returns {Object} the date parts for the specified number of steps away from this product's date.
					 */
					navigate: function(step) {
						return gfsStep(this.date(), step);
					},
					/**
					 * @param {Object} date the desired date parts or the string "current".
					 * @returns {Object} the actual date parts.
					 */
					navigateTo: function(date) {
						return gfsDate(date);
					},
					load: function(cancel) {
						var me = this;

						function loader(path) {
							return /\.epak([/?#]|$)/.test(path) ? µ.loadEpak(path) : µ.loadJson(path);
						}
						return when.map(this.paths, loader).then(function(files) {
							return cancel.requested ? null : _.extend(me, me.builder.apply(me, files));
						});
					},
					alpha: {
						single: 160,
						animated: 112
					},
				}, overrides);
			}

			/**
			 * @param {Object} date either the date parts or the string "current"
			 * @param {Object?} offset the time offset from the normal GFS time (e.g., {minute: 90} for 3hr time avg)
			 * @returns {Object} the best matching date parts.
			 */
			function gfsDate(date, offset) {
				var parts = date === "current" ?
					_.pick(utc.parts(clock.now()), "year", "month", "day", "hour") : // use current time
					_.clone(date);
				// round down to the nearest three-hour block
				parts.hour = Math.floor(parts.hour / 3) * 3;
				parts.minute = 0;
				if(offset) {
					parts = utc.normalize(utc.add(parts, offset));
				}
				return parts;
			}

			/**
			 * Returns date parts for the chronologically next or previous GFS data layer. How far forward or backward in time
			 * to jump is determined by the step. Steps of ±1 move in 3-hour jumps, and steps of ±10 move in 24-hour jumps.
			 *
			 * @param {Object} date the starting date parts.
			 * @param {number} step the number of steps.
			 * @returns {Object} the resulting date parts.
			 */
			function gfsStep(date, step) {
				var offset = (step > 1 ? 8 : step < -1 ? -8 : step) * 3;
				return utc.add(date, {
					hour: offset
				});
			}

			function gfsPathParts(attr, offset) {
				if(attr.date === "current") {
					return {
						dir: "current",
						stamp: "current",
						date: gfsDate(attr.date, offset)
					};
				}
				var date = gfsDate(attr.date, offset);
				return {
					dir: utc.print(date, "{yyyy}/{MM}/{dd}"),
					stamp: utc.print(date, "{hh}{mm}"),
					date: date
				};
			}

			/**
			 * @param attr
			 * @param {String} type
			 * @param {String?} surface
			 * @param {String?} level
			 * @returns {String}
			 */
			function gfsPath(attr, type, surface, level) {
				var parts = gfsPathParts(attr),
					date = utc.date(parts.date);
				var format = date >= new Date("2014/11/24") ? ".epak" : ".json";
				var res = "0.5";
				if(date < new Date("2015/03/11") || (µ.isEmbeddedInIFrame() && !µ.isKioskMode())) {
					res = "1.0";
				} else if(date > new Date("2016/06/22") && attr.hd) {
					res = "0.25";
				}
				var file = [parts.stamp, type, surface, level, "gfs", res].filter(µ.isValue).join("-") + format;
				return gfsUrl(parts.dir + "/" + file, date);
			}

			function geosPath(attr, type, offset) {
				var parts = gfsPathParts(attr, offset),
					date = utc.date(parts.date);
				var file = [parts.stamp, type, "geos.epak"].join("-");
				return geosUrl(parts.dir + "/" + file, date);
			}

			function wave30mPath(attr, type, surface, level) {
				var parts = gfsPathParts(attr),
					date = utc.date(parts.date);
				var file = [parts.stamp, type, surface, level, "wave", "30m"].filter(µ.isValue).join("-") + ".epak";
				return ww3Url(parts.dir + "/" + file, date);
			}

			/**
			 * Returns the file name for the most recent OSCAR data layer to the specified date. If offset is non-zero,
			 * the file name that many entries from the most recent is returned.
			 *
			 * The result is undefined if there is no entry for the specified date and offset can be found.
			 *
			 * UNDONE: the catalog object itself should encapsulate this logic. GFS can also be a "virtual" catalog, and
			 *         provide a mechanism for eliminating the need for /data/weather/current/* files.
			 *
			 * @param {Array} catalog array of file names, sorted and prefixed with yyyyMMdd. Last item is most recent.
			 * @param {Object} date parts or "current"
			 * @param {Number?} offset
			 * @returns {String} file name
			 */
			function lookupOscar(catalog, date, offset) {
				offset = +offset || 0;
				if(date === "current") {
					return catalog[catalog.length - 1 + offset];
				}
				var prefix = utc.print(date, "{yyyy}{MM}{dd}"),
					i = _.sortedIndex(catalog, prefix);
				i = (catalog[i] || "").indexOf(prefix) === 0 ? i : i - 1;
				return catalog[i + offset];
			}

			/**
			 * @param catalog
			 * @param date the date parts
			 * @returns {Object} date parts
			 */
			function oscarDate(catalog, date) {
				var file = lookupOscar(catalog, date);
				return file ? utc.parse(file, /(\d{4})(\d\d)(\d\d)/) : null;
			}

			/**
			 * @param {Array} catalog array of file names, sorted and prefixed with yyyyMMdd. Last item is most recent.
			 * @param {Object} date parts or the string "current".
			 * @param {number} step
			 * @returns {Object} the chronologically next or previous OSCAR data layer. How far forward or backward in
			 * time to jump is determined by the step and the catalog of available layers. A step of ±1 moves to the
			 * next/previous entry in the catalog (about 5 days), and a step of ±10 moves to the entry six positions away
			 * (about 30 days).
			 */
			function oscarStep(catalog, date, step) {
				var file = lookupOscar(catalog, date, step > 1 ? 6 : step < -1 ? -6 : step);
				return file ? utc.parse(file, /(\d{4})(\d\d)(\d\d)/) : null;
			}

			function oscarPath(catalog, attr) {
				var file = lookupOscar(catalog, attr.date);
				return file ? oscarUrl(file) : null;
			}

			function fetchOscarCatalog() {
				// The OSCAR catalog is an array of file names, sorted and prefixed with yyyyMMdd. Last item is the
				// most recent. For example: [ 20140101-abc.epak, 20140106-abc.epak, 20140112-abc.epak, ... ]
				return µ.loadJsonOnce(oscarUrl("oscar-catalog.json"));
			}

			function rtgDate(date) {
				var parts;
				if(date === "current") {
					// Each day, SST data for the previous day is made available. So "current" means yesterday.
					var now = _.pick(utc.parts(clock.now()), "year", "month", "day");
					parts = utc.add(now, {
						day: -1
					});
				} else {
					parts = _.clone(date);
				}
				return parts;
			}

			/**
			 * Returns a date for the chronologically next or previous RTG SST data layer. How far forward or backward in time
			 * to jump is determined by the step. Steps of ±1 move in 1 day jumps, and steps of ±10 move in 5-day jumps.
			 */
			function rtgStep(date, step) {
				var offset = step > 1 ? 5 : step < -1 ? -5 : step;
				return utc.add(date, {
					day: offset
				});
			}

			/**
			 * @param attr
			 * @param {String} type
			 * @param {String?} surface
			 * @param {String?} level
			 * @returns {String}
			 */
			function rtgPath(attr, type, surface, level) {
				var date = utc.date(rtgDate(attr.date));
				var dir = attr.date === "current" ? "current" : utc.print(attr.date, "{yyyy}/{MM}");
				var stamp = dir === "current" ? "current" : utc.print(attr.date, "{yyyy}{MM}{dd}");
				var file = [stamp, type, surface, level, "rtg", "0.5"].filter(µ.isValue).join("-") + ".epak";
				return rtgsstUrl(dir + "/" + file, date);
			}

			function describeSurface(attr) {
				return attr.surface === "surface" ? "Surface" : µ.capitalize(attr.level);
			}

			function describeSurfaceJa(attr) {
				return attr.surface === "surface" ? "地上" : µ.capitalize(attr.level);
			}

			/**
			 * Returns a function f(langCode) that, given table:
			 *     {foo: {en: "A", ja: "あ"}, bar: {en: "I", ja: "い"}}
			 * will return the following when called with "en":
			 *     {foo: "A", bar: "I"}
			 * or when called with "ja":
			 *     {foo: "あ", bar: "い"}
			 */
			function localize(table) {
				return function(langCode) {
					var result = {};
					_.each(table, function(value, key) {
						result[key] = value[langCode] || value.en || value;
					});
					return result;
				}
			}

			function localizeString(table) {
				return table[µ.siteLangCode()] || table.en;
			}

			var FACTORIES = {

				"wind": {
					matchesPrimary: function() {
						return true;
					}, // HACK: default matcher, must comes first.
					matchesOverlay: _.matches({
						overlayType: "wind"
					}),
					create: function(attr) {
						return buildProduct({
							type: "wind",
							descriptionHTML: localize({
								name: {
									en: "Wind",
									ja: "風速"
								},
								qualifier: {
									en: " @ " + describeSurface(attr),
									ja: " @ " + describeSurfaceJa(attr)
								}
							}),
							paths: [gfsPath(attr, "wind", attr.surface, attr.level)],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								return require("./product/gfs-wind.js")(file);
							},
							units: [{
								label: "km/h",
								conversion: function(x) {
									return x * 3.6;
								},
								precision: 0
							}, {
								label: "m/s",
								conversion: function(x) {
									return x;
								},
								precision: 1
							}, {
								label: "kn",
								conversion: function(x) {
									return x * 1.943844;
								},
								precision: 0
							}, {
								label: "mph",
								conversion: function(x) {
									return x * 2.236936;
								},
								precision: 0
							}],
							scale: require("./palette/wind.js"),
							particles: {
								velocityScale: 1 / 100,
								maxIntensity: 15
							},
						});
					}
				},

				"temp": {
					matchesOverlay: _.matches({
						overlayType: "temp"
					}),
					create: function(attr) {
						return buildProduct({
							type: "temp",
							descriptionHTML: localize({
								name: {
									en: "Temp",
									ja: "気温"
								},
								qualifier: {
									en: " @ " + describeSurface(attr),
									ja: " @ " + describeSurfaceJa(attr)
								}
							}),
							paths: [gfsPath(attr, "temp", attr.surface, attr.level)],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								// "Temperature_isobaric", "Temperature_height_above_ground"
								return scalarProduct(file, /Temperature/, strings.gfs, ["Temperature"]);
							},
							units: [{
								label: "°C",
								conversion: function(x) {
									return x - 273.15;
								},
								precision: 1
							}, {
								label: "°F",
								conversion: function(x) {
									return x * 9 / 5 - 459.67;
								},
								precision: 1
							}, {
								label: "K",
								conversion: function(x) {
									return x;
								},
								precision: 1
							}],
							scale: require("./palette/temp.js"),
						});
					}
				},

				"relative_humidity": {
					matchesOverlay: _.matches({
						overlayType: "relative_humidity"
					}),
					create: function(attr) {
						return buildProduct({
							type: "relative_humidity",
							descriptionHTML: localize({
								name: {
									en: "Relative Humidity",
									ja: "相対湿度"
								},
								qualifier: {
									en: " @ " + describeSurface(attr),
									ja: " @ " + describeSurfaceJa(attr)
								}
							}),
							paths: [gfsPath(attr, "relative_humidity", attr.surface, attr.level)],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								// "Relative_humidity_isobaric", "Relative_humidity_height_above_ground"
								return scalarProduct(file, /Relative_humidity/, strings.gfs);
							},
							units: [{
								label: "%",
								conversion: function(x) {
									return x;
								},
								precision: 0
							}],
							scale: {
								bounds: [0, 100],
								gradient: µ.segmentedColorScale([
									[0, [230, 165, 30]],
									[25, [120, 100, 95]],
									[60, [40, 44, 92]],
									[75, [21, 13, 193]],
									[90, [75, 63, 235]],
									[100, [25, 255, 255]]
								])
							}
						});
					}
				},

				"air_density": {
					matchesOverlay: _.matches({
						overlayType: "air_density"
					}),
					create: function(attr) {
						return buildProduct({
							type: "air_density",
							descriptionHTML: localize({
								name: {
									en: "Air Density",
									ja: "空気密度"
								},
								qualifier: {
									en: " @ " + describeSurface(attr),
									ja: " @ " + describeSurfaceJa(attr)
								}
							}),
							paths: [gfsPath(attr, "air_density", attr.surface, attr.level)],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								return scalarProduct(file, /air_density/, strings.gfs);
							},
							units: [{
								label: "kg/m³",
								conversion: function(x) {
									return x;
								},
								precision: 2
							}],
							scale: {
								bounds: [0, 1.5],
								gradient: function(v, a) {
									return µ.sinebowColor(Math.min(v, 1.5) / 1.5, a);
								}
							}
						});
					}
				},

				"wind_power_density": {
					matchesOverlay: _.matches({
						overlayType: "wind_power_density"
					}),
					create: function(attr) {
						var windProduct = FACTORIES.wind.create(attr);
						var airdensProduct = FACTORIES.air_density.create(attr);
						return buildProduct({
							type: "wind_power_density",
							descriptionHTML: localize({
								name: {
									en: "Instant Wind Power Density",
									ja: "風力エネルギー密度"
								},
								qualifier: {
									en: " @ " + describeSurface(attr),
									ja: " @ " + describeSurfaceJa(attr)
								}
							}),
							paths: [windProduct.paths[0], airdensProduct.paths[0]],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(windFile, airdensFile) {
								return require("./product/gfs-wpd.js")(windFile, airdensFile);
							},
							units: [{
								label: "kW/m<sup>2</sup>",
								tooltip: "kW/m²",
								conversion: function(x) {
									return x / 1000;
								},
								precision: 1
							}, {
								label: "W/m<sup>2</sup>",
								tooltip: "W/m²",
								conversion: function(x) {
									return x;
								},
								precision: 0
							}],
							scale: {
								bounds: [0, 80000],
								gradient: µ.segmentedColorScale([
									[0, [15, 4, 96]],
									[250, [30, 8, 180]],
									[1000, [121, 102, 2]],
									[2000, [118, 161, 66]],
									[4000, [50, 102, 219]],
									[8000, [19, 131, 193]],
									[16000, [59, 204, 227]],
									[64000, [241, 1, 45]],
									[80000, [243, 0, 241]]
								])
							}
						});
					}
				},

				"misery_index": {
					matchesOverlay: _.matches({
						overlayType: "misery_index"
					}),
					create: function(attr) {
						return buildProduct({
							type: "misery_index",
							descriptionHTML: localize({
								name: {
									en: "Misery (Wind Chill & Heat Index)",
									ja: "体感温度"
								},
								qualifier: {
									en: " @ " + describeSurface(attr),
									ja: " @ " + describeSurfaceJa(attr)
								}
							}),
							paths: [gfsPath(attr, "misery_index")],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								return scalarProduct(file, /misery_index/, strings.gfs);
							},
							units: function() {
								var perceived = localizeString({
									en: " (feels like)",
									ja: "（体感温度）"
								});
								return [{
									label: "°C" + perceived,
									conversion: function(x) {
										return x - 273.15;
									},
									precision: 1
								}, {
									label: "°F" + perceived,
									conversion: function(x) {
										return x * 9 / 5 - 459.67;
									},
									precision: 1
								}, {
									label: "K" + perceived,
									conversion: function(x) {
										return x;
									},
									precision: 1
								}];
							}(),
							scale: {
								bounds: [236, 332],
								gradient: µ.segmentedColorScale([
									[241, [255, 255, 255]], // -32 C, -25 F extreme frostbite
									[245.5, [6, 82, 255]],
									[250, [6, 82, 255]], // -23 C, -10 F frostbite
									[258, [46, 131, 255]],
									[266, [46, 131, 255]], // -7 C, 20 F hypothermia
									[280, [0, 0, 0]], // 7 C, 45 F begin suckage (cold)
									[300, [0, 0, 0]], // 27 C, 80 F begin caution (heat)
									[305, [247, 20, 35]], // 32 C, 90 F extreme caution
									[309.5, [247, 20, 35]],
									[314, [245, 210, 5]], // 41 C, 105 F danger
									[320.5, [245, 210, 5]],
									[327, [255, 255, 255]] // 54 C, 130 F extreme danger
								])
							}
						});
					}
				},

				"total_cloud_water": {
					matchesOverlay: _.matches({
						overlayType: "total_cloud_water"
					}),
					create: function(attr) {
						return buildProduct({
							type: "total_cloud_water",
							descriptionHTML: localize({
								name: {
									en: "Total Cloud Water",
									ja: "雲水量"
								},
								qualifier: ""
							}),
							paths: [gfsPath(attr, "total_cloud_water")],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								// "Cloud_water_entire_atmosphere", "Cloud_water_entire_atmosphere_single_layer"
								return scalarProduct(file, /Cloud_water/, strings.gfs, ["Cloud_water"]);
							},
							units: [{
								label: "kg/m<sup>2</sup>",
								tooltip: "kg/m²",
								conversion: function(x) {
									return x;
								},
								precision: 3
							}],
							scale: {
								bounds: [0, 1],
								gradient: µ.segmentedColorScale([
									[0.0, [5, 5, 89]],
									[0.2, [170, 170, 230]],
									[1.0, [255, 255, 255]]
								])
							}
						});
					}
				},

				"total_precipitable_water": {
					matchesOverlay: _.matches({
						overlayType: "total_precipitable_water"
					}),
					create: function(attr) {
						return buildProduct({
							type: "total_precipitable_water",
							descriptionHTML: localize({
								name: {
									en: "Total Precipitable Water",
									ja: "可降水量"
								},
								qualifier: ""
							}),
							paths: [gfsPath(attr, "total_precipitable_water")],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								// "Precipitable_water_entire_atmosphere", "Precipitable_water_entire_atmosphere_single_layer"
								return scalarProduct(file, /Precipitable_water/, strings.gfs, ["Precipitable_water"]);
							},
							units: [{
								label: "kg/m<sup>2</sup>",
								tooltip: "kg/m²",
								conversion: function(x) {
									return x;
								},
								precision: 3
							}],
							scale: {
								bounds: [0, 70],
								gradient: µ.segmentedColorScale([
									[0, [230, 165, 30]],
									[10, [120, 100, 95]],
									[20, [40, 44, 92]],
									[30, [21, 13, 193]],
									[40, [75, 63, 235]],
									[60, [25, 255, 255]],
									[70, [150, 255, 255]]
								])
							}
						});
					}
				},

				"mean_sea_level_pressure": {
					matchesOverlay: _.matches({
						overlayType: "mean_sea_level_pressure"
					}),
					create: function(attr) {
						return buildProduct({
							type: "mean_sea_level_pressure",
							descriptionHTML: localize({
								name: {
									en: "Mean Sea Level Pressure",
									ja: "海面更正気圧"
								},
								qualifier: ""
							}),
							paths: [gfsPath(attr, "mean_sea_level_pressure")],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								// Pressure_reduced_to_MSL_msl
								return scalarProduct(file, /Pressure_reduced_to_MSL/, strings.gfs, ["Pressure_reduced_to_MSL"]);
							},
							units: [{
								label: "hPa",
								conversion: function(x) {
									return x / 100;
								},
								precision: 0
							}, {
								label: "mmHg",
								conversion: function(x) {
									return x / 133.322387415;
								},
								precision: 0
							}, {
								label: "inHg",
								conversion: function(x) {
									return x / 3386.389;
								},
								precision: 1
							}],
							scale: {
								bounds: [92000, 105000],
								gradient: µ.segmentedColorScale([
									[92000, [40, 0, 0]],
									[95000, [187, 60, 31]],
									[96500, [137, 32, 30]],
									[98000, [16, 1, 43]],
									[100500, [36, 1, 93]],
									[101300, [241, 254, 18]],
									[103000, [228, 246, 223]],
									[105000, [255, 255, 255]]
								])
							}
						});
					}
				},

				"precip_3hr": {
					matchesOverlay: _.matches({
						overlayType: "precip_3hr"
					}),
					create: function(attr) {
						return buildProduct({
							type: "precip_3hr",
							descriptionHTML: localize({
								name: {
									en: "Next 3-hr Precip Accumulation",
									ja: "3時間の降水量"
								},
								qualifier: "",
							}),
							paths: [gfsPath(attr, "precip_3hr")],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								return scalarProduct(file, /precip_accumulation_3hr/, strings.gfs);
							},
							units: [{
								label: "mm",
								tooltip: "mm",
								conversion: function(x) {
									return x;
								},
								precision: 1
							}, {
								label: "in",
								tooltip: "in",
								conversion: function(x) {
									return x / 25.4;
								},
								precision: 2
							}, {
								label: "kg/m<sup>2</sup>",
								tooltip: "kg/m²",
								conversion: function(x) {
									return x;
								},
								precision: 1
							}],
							alpha: {
								single: 160,
								animated: 160
							},
							scale: require("./palette/precip"),
						});
					}
				},

				"cape": {
					matchesOverlay: _.matches({
						overlayType: "cape"
					}),
					create: function(attr) {
						return buildProduct({
							type: "cape",
							descriptionHTML: localize({
								name: {
									en: "CAPE (Surface)",
									ja: "対流有効位置エネルギー（地上）"
								},
								qualifier: "",
							}),
							paths: [gfsPath(attr, "cape")],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								return scalarProduct(file, /Convective_available_potential_energy_surface/, strings.gfs);
							},
							units: [{
								label: "J/kg",
								tooltip: "J/kg",
								conversion: function(x) {
									return x;
								},
								precision: 0
							}],
							alpha: {
								single: 160,
								animated: 140
							},
							scale: require("./palette/cape"),
						});
					}
				},

				"co2": {
					matchesOverlay: _.matches({
						overlayType: "co2"
					}),
					create: function(attr) {
						return buildProduct({
							type: "co2",
							descriptionHTML: localize({
								name: {
									en: "Carbon Dioxide Mixing Ratio",
									ja: "二酸化炭素混合比"
								},
								qualifier: ""
							}),
							paths: [geosPath(attr, "co2")],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								return scalarProduct(file, /CO2CL/, strings.geos);
							},
							units: [ // CO2 Bulk Mixing Ratio (Column Mass/ps), units: mol/mol
								// {label: "ppmv", conversion: function(x) { return x * 1e6; }, precision: 1},
								{
									label: "µmol/mol",
									conversion: function(x) {
										return x * 1e6;
									},
									precision: 1
								}
							],
							alpha: {
								single: 160,
								animated: 160
							},
							scale: require("./palette/co2"),
						});
					}
				},

				"cosc": {
					matchesOverlay: _.matches({
						overlayType: "cosc"
					}),
					create: function(attr) {
						return buildProduct({
							type: "cosc",
							descriptionHTML: localize({
								name: {
									en: "Carbon Monoxide Conc.",
									ja: "一酸化炭素濃度"
								},
								qualifier: {
									en: " @ Surface",
									ja: " @ 地上"
								}
							}),
							paths: [geosPath(attr, "cosc")],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								return scalarProduct(file, /COSC/, strings.geos);
							},
							units: [ // CO Surface Concentration in ppbv, units: 1e-9
								{
									label: "ppbv",
									conversion: function(x) {
										return x;
									},
									precision: 0
								}, {
									label: "ppmv",
									conversion: function(x) {
										return x / 1000;
									},
									precision: 2
								}
							],
							alpha: {
								single: 160,
								animated: 140
							},
							scale: require("./palette/cosc"),
						});
					}
				},

				"so2smass": {
					matchesOverlay: _.matches({
						overlayType: "so2smass"
					}),
					create: function(attr) {
						return buildProduct({
							type: "so2smass",
							descriptionHTML: localize({
								name: {
									en: "Sulfur Dioxide Mass",
									ja: "二酸化硫黄質量"
								},
								qualifier: {
									en: " @ Surface",
									ja: " @ 地上"
								}
							}),
							paths: [geosPath(attr, "so2smass")],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								return scalarProduct(file, /SO2SMASS/, strings.geos);
							},
							units: [ // SO2 Surface Mass Concentration, units: kg/m3
								//{label: "ppb", conversion: function(x) { return x * 1000 * 1000 * 1000 / 2.86; }, precision: 3},
								//{label: "ppm", conversion: function(x) { return x * 1000 * 1000 / 2.86; }, precision: 3},
								{
									label: "µg/m<sup>3</sup>",
									tooltip: "µg/m³",
									conversion: function(x) {
										return x * 1000 * 1000000;
									},
									precision: 2
								}
								//{label: "mg/m<sup>3</sup>", tooltip: "mg/m³", conversion: function(x) { return x * 1000 * 1000; }, precision: 3}
								//{label: "kg/m<sup>3</sup>", conversion: function(x) { return x; }, precision: 14}
							],
							alpha: {
								single: 160,
								animated: 140
							},
							scale: require("./palette/so2smass"),
						});
					}
				},

				"duexttau": {
					matchesOverlay: _.matches({
						overlayType: "duexttau"
					}),
					create: function(attr) {
						return buildProduct({
							type: "duexttau",
							descriptionHTML: localize({
								name: {
									en: "Dust Extinction " + aotLink,
									ja: "粒子消散係数 " + aotLink
								},
								qualifier: ""
							}),
							paths: [geosPath(attr, "duexttau")],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								return scalarProduct(file, /DUEXTTAU/, strings.geos);
							},
							units: [ // Dust Extinction AOT (aerosol optical thickness) [550 nm], units: τ
								{
									label: "τ",
									conversion: function(x) {
										return x;
									},
									precision: 4
								}
							],
							alpha: {
								single: 160,
								animated: 140
							},
							scale: require("./palette/duexttau"),
						});
					}
				},

				"suexttau": {
					matchesOverlay: _.matches({
						overlayType: "suexttau"
					}),
					create: function(attr) {
						return buildProduct({
							type: "suexttau",
							descriptionHTML: localize({
								name: {
									en: "Sulfate Extinction " + aotLink,
									ja: "硫酸塩消散係数 " + aotLink
								},
								qualifier: ""
							}),
							paths: [geosPath(attr, "suexttau")],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								return scalarProduct(file, /SUEXTTAU/, strings.geos);
							},
							units: [ // SO4 Extinction AOT (aerosol optical thickness) [550 nm], units: τ
								{
									label: "τ",
									conversion: function(x) {
										return x;
									},
									precision: 3
								}
							],
							alpha: {
								single: 160,
								animated: 140
							},
							scale: require("./palette/suexttau"),
						});
					}
				},

				"co2sc": {
					matchesOverlay: _.matches({
						overlayType: "co2sc"
					}),
					create: function(attr) {
						var Δ = 32;
						return buildProduct({
							type: "co2sc",
							descriptionHTML: localize({
								name: {
									en: "Carbon Dioxide Concentration",
									ja: "二酸化炭素濃度"
								},
								qualifier: {
									en: " @ Surface",
									ja: " @ 地上"
								}
							}),
							paths: [geosPath(attr, "co2sc", {
								minute: 90
							})],
							date: function() {
								return gfsDate(attr.date, {
									minute: 90
								});
							},
							navigate: function(step) {
								return gfsStep(this.date(), step);
							},
							navigateTo: function(date) {
								return gfsDate(date, {
									minute: 90
								});
							},
							builder: function(file) {
								console.log("builder", file);
								return scalarProduct(file, /CO2SC/, strings.geosCO2, null, function(data) {
									for(var i = 0; i < data.length; i++) {
										data[i] += Δ;
									}
								});
							},
							units: [ // CO2 Surface Concentration, units: ppmv
								{
									label: "ppmv",
									conversion: function(x) {
										return x;
									},
									precision: 0
								}
							],
							alpha: {
								single: 200,
								animated: 150
							},
							scale: require("./palette/co2sc")(Δ),
						});
					}
				},

				"currents": {
					matchesPrimary: _.matches({
						surface: "surface",
						level: "currents"
					}),
					matchesOverlay: _.matches({
						overlayType: "currents"
					}),
					create: function(attr) {
						return when(fetchOscarCatalog()).then(function(catalog) {
							return buildProduct({
								type: "currents",
								descriptionHTML: localize({
									name: {
										en: "Ocean Currents",
										ja: "海流"
									},
									qualifier: {
										en: " @ Surface",
										ja: " @ 地上"
									}
								}),
								paths: [oscarPath(catalog, attr)],
								date: function() {
									return oscarDate(catalog, attr.date);
								},
								navigate: function(step) {
									return oscarStep(catalog, this.date(), step);
								},
								navigateTo: function(date) {
									return oscarStep(catalog, date, 0);
								},
								builder: function(file) {
									return require("./product/oscar.js")(file);
								},
								units: [{
									label: "m/s",
									conversion: function(x) {
										return x;
									},
									precision: 2
								}, {
									label: "km/h",
									conversion: function(x) {
										return x * 3.6;
									},
									precision: 1
								}, {
									label: "kn",
									conversion: function(x) {
										return x * 1.943844;
									},
									precision: 1
								}, {
									label: "mph",
									conversion: function(x) {
										return x * 2.236936;
									},
									precision: 1
								}],
								scale: {
									bounds: [0, 1.5],
									gradient: µ.segmentedColorScale([
										[0, [10, 25, 68]],
										[0.15, [10, 25, 250]],
										[0.4, [24, 255, 93]],
										[0.65, [255, 233, 102]],
										[1.0, [255, 233, 15]],
										[1.5, [255, 15, 15]]
									])
								},
								particles: {
									velocityScale: 1 / 7,
									maxIntensity: 0.7
								}
							});
						});
					}
				},

				"primary_waves": {
					matchesPrimary: _.matches({
						param: "ocean",
						surface: "primary",
						level: "waves"
					}),
					matchesOverlay: _.matches({
						overlayType: "primary_waves"
					}),
					create: function(attr) {
						return buildProduct({
							type: "primary_waves",
							descriptionHTML: localize({
								name: {
									en: "Peak Wave Period",
									ja: "ピーク波周期"
								},
								qualifier: ""
							}),
							paths: [wave30mPath(attr, "primary")],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								return require("./product/ww3-primary")(file);
							},
							units: [{
								label: "sec",
								conversion: function(x) {
									return x;
								},
								precision: 1
							}],
							scale: {
								bounds: [0, 25],
								gradient: µ.segmentedColorScale([
									[0, [0, 0, 0]],
									[25, [21, 255, 255]]
								])
							},
							particles: {
								velocityScale: 1 / 612,
								maxIntensity: 12,
								waves: true
							}
						});
					}
				},

				"sea_surface_temp": {
					matchesOverlay: _.matches({
						overlayType: "sea_surface_temp"
					}),
					create: function(attr) {
						return buildProduct({
							type: "sea_surface_temp",
							descriptionHTML: localize({
								name: {
									en: "Sea Surface Temp",
									ja: "海面水温"
								},
								qualifier: ""
							}),
							paths: [rtgPath(attr, "sea_surface_temp")],
							date: function() {
								return rtgDate(attr.date);
							},
							navigate: function(step) {
								return rtgStep(this.date(), step);
							},
							navigateTo: function(date) {
								return rtgStep(date, 0);
							},
							builder: function(file) {
								return scalarProduct(file, /Temperature_surface_sparse/, strings.rtgsst);
							},
							units: [{
								label: "°C",
								conversion: function(x) {
									return x - 273.15;
								},
								precision: 1
							}, {
								label: "°F",
								conversion: function(x) {
									return x * 9 / 5 - 459.67;
								},
								precision: 1
							}, {
								label: "K",
								conversion: function(x) {
									return x;
								},
								precision: 1
							}],
							scale: {
								bounds: [270, 304.65],
								gradient: µ.segmentedColorScale([
									[270, [255, 255, 255]],
									[271.25, [255, 255, 255]], // -1.9 C sea water freeze
									[271.30, [15, 4, 168]],
									[273.15, [15, 54, 208]], // 0 C fresh water freeze
									[273.25, [15, 54, 188]],
									[275.65, [15, 4, 168]], // lower boundary for cool currents
									[281.65, [24, 132, 14]], // upper boundary for cool currents
									[291.15, [247, 251, 59]], // lower boundary for warm currents
									[295, [235, 167, 0]],
									[299.65, [245, 0, 39]], // minimum needed for tropical cyclone formation
									[303, [87, 17, 0]],
									[304.65, [238, 0, 242]]
								])
							}
						});
					}
				},

				"sea_surface_temp_anomaly": {
					matchesOverlay: _.matches({
						overlayType: "sea_surface_temp_anomaly"
					}),
					create: function(attr) {
						return buildProduct({
							type: "sea_surface_temp_anomaly",
							descriptionHTML: localize({
								name: {
									en: "SST Anomaly",
									ja: "海面水温異常"
								},
								qualifier: ""
							}),
							paths: [rtgPath(attr, "sea_surface_temp_anomaly")],
							date: function() {
								return rtgDate(attr.date);
							},
							navigate: function(step) {
								return rtgStep(this.date(), step);
							},
							navigateTo: function(date) {
								return rtgStep(date, 0);
							},
							builder: function(file) {
								return scalarProduct(file, /Temperature_surface_anomaly_sparse/, strings.rtgsst);
							},
							units: [{
								label: "°C",
								conversion: function(x) {
									return x;
								},
								precision: 1
							}, {
								label: "°F",
								conversion: function(x) {
									return x * 9 / 5;
								},
								precision: 1
							}, {
								label: "K",
								conversion: function(x) {
									return x;
								},
								precision: 1
							}],
							scale: {
								bounds: [-6, 6],
								gradient: µ.segmentedColorScale([
									[-6.0, [255, 255, 255]],
									[-3, [7, 252, 254]],
									[-1.5, [66, 42, 253]],
									[-0.75, [34, 55, 134]],
									[0, [0, 0, 6]],
									[0.75, [134, 55, 34]],
									[1.5, [253, 14, 16]],
									[3.0, [254, 252, 0]],
									[6.0, [255, 255, 255]]
								])
							}
						});
					}
				},

				"significant_wave_height": {
					matchesOverlay: _.matches({
						overlayType: "significant_wave_height"
					}),
					create: function(attr) {
						return buildProduct({
							type: "significant_wave_height",
							descriptionHTML: localize({
								name: {
									en: "Significant Wave Height",
									ja: "有義波高"
								},
								qualifier: ""
							}),
							paths: [wave30mPath(attr, "sig_height")],
							date: function() {
								return gfsDate(attr.date);
							},
							builder: function(file) {
								// "Significant_height_of_combined_wind_waves_and_swell_surface"
								return scalarProduct(file, /Significant_height/, strings.ww3);
							},
							units: [{
								label: "m",
								conversion: function(x) {
									return x;
								},
								precision: 2
							}, {
								label: "ft",
								conversion: function(x) {
									return x * 100 / 2.54 / 12;
								},
								precision: 1
							}],
							scale: {
								bounds: [0, 15],
								gradient: µ.segmentedColorScale([
									[0, [8, 29, 88]],
									[1, [37, 52, 148]],
									[2, [34, 94, 168]],
									[3, [29, 145, 192]],
									[4, [65, 182, 196]],
									[5, [127, 205, 187]],
									[6, [199, 233, 180]],
									[7, [237, 248, 177]],
									[8, [254, 204, 92]],
									[10, [253, 141, 60]],
									[12, [240, 59, 32]],
									[14, [189, 0, 38]]
								])
							}
						});
					}
				},

				"off": {
					matchesOverlay: _.matches({
						overlayType: "off"
					}),
					create: function() {
						return null;
					}
				}
			};

			products.productsFor = function(attributes) {
				var attr = _.clone(attributes),
					primary = null,
					overlay = null;
				_.values(FACTORIES).forEach(function(factory) {
					if(_.isFunction(factory.matchesPrimary) && factory.matchesPrimary(attr)) {
						primary = factory;
					}
					if(_.isFunction(factory.matchesOverlay) && factory.matchesOverlay(attr) && factory !== primary) {
						overlay = factory;
					}
				});
				var results = [];
				if(primary) results.push(primary.create(attr));
				if(overlay) results.push(overlay.create(attr));
				return results.filter(µ.isTruthy);
			};

			products.overlayTypes = function() {
				return d3.set(_.keys(FACTORIES));
			};

			products.argoUrl = argoUrl;

			return products;
		};

	}, {
		"./clock": 25,
		"./micro": 39,
		"./palette/cape": 40,
		"./palette/co2": 41,
		"./palette/co2sc": 42,
		"./palette/cosc": 43,
		"./palette/duexttau": 44,
		"./palette/precip": 47,
		"./palette/so2smass": 48,
		"./palette/suexttau": 49,
		"./palette/temp.js": 50,
		"./palette/wind.js": 51,
		"./product/gfs-wind.js": 52,
		"./product/gfs-wpd.js": 53,
		"./product/oscar.js": 54,
		"./product/scalarProduct": 55,
		"./product/strings": 56,
		"./product/ww3-primary": 58,
		"./utc": 61,
		"d3": 3,
		"underscore": 5,
		"when": 23
	}],
	60: [function(require, module, exports) {
		"use strict";

		/*
		 * Orthographic projection. Adapted from:
		 *    Map Projections: A Working Manual, Snyder, John P: pubs.er.usgs.gov/publication/pp1395
		 *    See page 145.
		 *
		 * γ rotation is not yet supported.
		 */

		var µ = require("./../micro");

		var π = Math.PI,
			τ = 2 * π,
			DEG = 360 / τ,
			RAD = τ / 360;

		/**
		 * @param {number} R radius of the sphere (i.e., scale)
		 * @param {number} λ0 longitude of projection center (degrees)
		 * @param {number} φ0 latitude of projection center (degrees)
		 * @param {number} x0 translation along x axis
		 * @param {number} y0 translation along y axis
		 * @returns {Function} projection function f([λ, φ]) and f.invert([x, y]), just like D3.
		 */
		function orthographic(R, λ0, φ0, x0, y0) {

			// Check if φ0 is rotated far enough that the globe is upside down. If so, adjust the projection center and
			// flip the x,y space. For example, rotation of +100 is actually lat of 80 deg with lon on other side.

			var φnorm = µ.floorMod(φ0 + 90, 360); // now on range [0, 360). Anything on range (180, 360) is flipped.
			var flip = 180 < φnorm ? -1 : 1;
			if(flip < 0) {
				φ0 = 270 - φnorm;
				λ0 += 180;
			} else {
				φ0 = φnorm - 90;
			}
			φ0 *= RAD;
			λ0 = (µ.floorMod(λ0 + 180, 360) - 180) * RAD; // normalize to [-180, 180)

			var R2 = R * R;
			var sinφ0 = Math.sin(φ0);
			var cosφ0 = Math.cos(φ0);
			var Rcosφ0 = R * cosφ0;
			var cosφ0dR = cosφ0 / R;

			/**
			 * @param {number[]} coord [λ, φ] in degrees
			 * @returns {number[]} resulting [x, y] or [NaN, NaN] if the coordinates are not defined for the projection.
			 */
			function project(coord) {
				var lon = coord[0];
				var lat = coord[1];
				if(lon !== lon || lat !== lat) {
					return [NaN, NaN];
				}
				var λ = lon * RAD;
				var φ = lat * RAD;
				var Δλ = λ - λ0;
				var sinΔλ = Math.sin(Δλ);
				var cosΔλ = Math.cos(Δλ);
				var sinφ = Math.sin(φ);
				var cosφ = Math.cos(φ);
				var Rcosφ = R * cosφ;
				//var cosc = sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ;  // test if clip angle > 90°
				//if (cosc < 0) return [NaN, NaN];
				var x = Rcosφ * sinΔλ;
				var y = Rcosφ * cosΔλ * sinφ0 - Rcosφ0 * sinφ; // negates y because it grows downward
				var px = x * flip + x0;
				var py = y * flip + y0;
				return [px, py];
			}

			/**
			 * @param {number[]} point [x, y]
			 * @returns {number[]} resulting [λ, φ] in degrees or [NaN, NaN] if the point is not defined for the projection.
			 */
			function invert(point) {
				var px = point[0];
				var py = point[1];
				var x = (px - x0) * flip;
				var y = (y0 - py) * flip; // negate y because it grows downward

				// var ρ = Math.sqrt(x * x + y * y);   // positive number
				// var c = Math.asin(ρ / R);           // [0, π/2] or NaN when ρ > R (meaning the point is outside the globe)
				// var sinc = Math.sin(c);             // [0, 1] because c in range [0, π/2]
				// var cosc = Math.cos(c);             // [0, 1] because c in range [0, π/2]
				// var ysinc = y * sinc;
				// var λ = λ0 + Math.atan2(x * sinc, ρ * cosc * cosφ0 - ysinc * sinφ0);
				// var φ = ρ === 0 ? φ0 : Math.asin(cosc * sinφ0 + ysinc * cosφ0 / ρ);

				var ρ2 = x * x + y * y;
				var d = 1 - ρ2 / R2;
				if(d >= 0) {
					var cosc = Math.sqrt(d); // cos(asin(x)) == sqrt(1 - x*x)
					var λ = λ0 + Math.atan2(x, cosc * Rcosφ0 - y * sinφ0);
					var φ = Math.asin(cosc * sinφ0 + y * cosφ0dR);
					return [λ * DEG, φ * DEG];
				}
				return [NaN, NaN]; // outside of projection
			}

			project.invert = invert;

			return project;
		}

		/**
		 * @param p d3 version of orthographic projection.
		 * @returns {Function} projection function f([λ, φ]) and f.invert([x, y]), just like D3.
		 */
		orthographic.fromD3 = function(p) {
			var t = p.translate(),
				r = p.rotate();
			if(r[2] !== 0) {
				throw new Error("γ rotation not supported");
			}
			return orthographic(p.scale(), -r[0], -r[1], t[0], t[1]);
		};

		module.exports = orthographic;

	}, {
		"./../micro": 39
	}],
	61: [function(require, module, exports) {
		/**
		 * utc: utilities for working with datetimes.
		 *
		 * CONSIDER: a datetime starts at year, a duration is a partial datetime. So is {year: 2015} a datetime or a duration?
		 * How about {year: 2016, month: 2, day: 14}?
		 */
		! function() {
			"use strict";

			var utc = module.exports = {};

			var _ = require("underscore");
			var all = ["year", "month", "day", "hour", "minute", "second", "milli"];

			function coalesce(a, b) {
				return a !== undefined && a !== null ? a : b;
			}

			/**
			 * Returns the string representation of a number padded with leading characters to make
			 * it at least "width" length.
			 *
			 * @param {Number} n the number to convert to a padded string
			 * @param {Number} width the desired minimum width of the resulting string
			 * @param {string} [char] the character to use for padding, default is "0"
			 * @returns {string} the padded string
			 */
			function pad(n, width, char) {
				var s = n.toString();
				var i = Math.max(width - s.length, 0);
				return new Array(i + 1).join(char || "0") + s;
			}

			/**
			 * @param {Date|String|Number} date a Date object, or parsable date string (Note: "yyyy-MM-ddThh:mm:ss" and its
			 *        prefixes are interpreted in UTC zone.)
			 * @returns {Date} a Date object
			 */
			function asDate(date) {
				date = coalesce(date, "");
				if(_.isString(date) || _.isNumber(date)) {
					date = new Date(date);
				}
				return date;
			}

			/**
			 * @param {Date|String|Number} date a Date object, or parsable date string (Note: "yyyy-MM-ddThh:mm:ss" and its
			 *        prefixes are interpreted in UTC zone.)
			 * @returns {Object} all UTC parts of the date: "year", "month", "day", "hour", "minute", "second", "milli"
			 */
			utc.parts = function(date) {
				date = asDate(date);
				return {
					year: date.getUTCFullYear(),
					month: date.getUTCMonth() + 1,
					day: date.getUTCDate(),
					hour: date.getUTCHours(),
					minute: date.getUTCMinutes(),
					second: date.getUTCSeconds(),
					milli: date.getUTCMilliseconds()
				};
			};

			/**
			 * @param {Date|String|Number} date a Date object, or parsable date string (Note: "yyyy-MM-ddThh:mm:ss" and its
			 *        prefixes are interpreted in UTC zone.)
			 * @returns {Object} all Local parts of the date: "year", "month", "day", "hour", "minute", "second", "milli"
			 */
			utc.localParts = function(date) {
				date = asDate(date);
				return {
					year: date.getFullYear(),
					month: date.getMonth() + 1,
					day: date.getDate(),
					hour: date.getHours(),
					minute: date.getMinutes(),
					second: date.getSeconds(),
					milli: date.getMilliseconds()
				};
			};

			/**
			 * @param {Object} parts the UTC date parts.
			 * @returns {Date} the Date representation of the specified parts.
			 */
			utc.date = function(parts) {
				return new Date(Date.UTC(
					coalesce(parts.year, 1900),
					coalesce(parts.month, 1) - 1,
					coalesce(parts.day, 1),
					coalesce(parts.hour, 0),
					coalesce(parts.minute, 0),
					coalesce(parts.second, 0),
					coalesce(parts.milli, 0)));
			};

			/**
			 * Adjusts UTC date parts so that they represent an actual date. Parts that overflow, like {hour: 36}, are
			 * adjusted to their proper range by carrying-over to the next larger part. Missing parts are added.
			 *
			 * @param parts the UTC date parts to normalize.
			 * @returns {{year: number, month: number, day: number, hour: number, minute: number, second: number, milli:
			 *     number}} all UTC parts adjusted to represent an actual date.
			 */
			utc.normalize = function(parts) {
				return utc.parts(utc.date(parts));
			};

			/**
			 * @param {Object} parts the UTC date parts.
			 * @param {Object} delta the parts to add. For example: {hour: 1, minute: 30}.
			 * @returns {{year: number, month: number, day: number, hour: number, minute: number, second: number, milli:
			 *     number}} all UTC parts with the delta added.
			 */
			utc.add = function(parts, delta) {
				var result = _.clone(parts);
				_.intersection(_.keys(delta), all).forEach(function(key) {
					result[key] = +coalesce(result[key], 0) + (+delta[key]);
				});
				return result;
			};

			/**
			 * @returns {Number} standard comparator result. Both arguments are converted to Unix millis then compared.
			 *          Invalid dates are smaller/earlier than all valid dates.
			 */
			utc.compare = function(aParts, bParts) {
				var a = utc.date(aParts).getTime();
				if(isNaN(a)) {
					a = -Infinity;
				}
				var b = utc.date(bParts).getTime();
				if(isNaN(b)) {
					b = -Infinity;
				}
				return a < b ? -1 : a > b ? 1 : 0;
			};

			/**
			 * @param {Object} parts the UTC date parts.
			 * @param {string} format the format specification. Example: "{yyyy}-{MM}-{dd}T{hh}:{mm}:{ss}.{SSS}"
			 * @returns {string} the formatted date.
			 */
			utc.print = function(parts, format) {
				var builder = [];
				for(var i = 0; i < format.length; i++) {
					var c = format[i];
					if(c !== "{") {
						builder.push(c);
						continue;
					}
					var spec = "";
					for(i++; i < format.length; i++) {
						c = format[i];
						if(c !== "}") {
							spec += c;
							continue;
						}
						var value = NaN;
						switch(spec[0]) {
							case "y":
								value = +parts.year;
								break;
							case "M":
								value = +parts.month;
								break;
							case "d":
								value = +parts.day;
								break;
							case "h":
								value = +parts.hour;
								break;
							case "m":
								value = +parts.minute;
								break;
							case "s":
								value = +parts.second;
								break;
							case "S":
								value = +parts.milli;
								break;
						}
						if(value === value) {
							builder.push(pad(value, spec.length));
						} else {
							builder.push("{", spec, "}");
						}
						break;
					}
				}
				return builder.join("");
			};

			utc.parse = function(s, format, groups) {
				var parts = {};

				function assign(key, value) {
					if(value === value) {
						parts[key] = value;
					}
				}

				groups = groups || {
					year: 1,
					month: 2,
					day: 3,
					hour: 4,
					minute: 5,
					second: 6,
					milli: 7
				};
				var match = format.exec(s);
				if(match) {
					assign("year", +match[groups.year]);
					assign("month", +match[groups.month]);
					assign("day", +match[groups.day]);
					assign("hour", +match[groups.hour]);
					assign("minute", +match[groups.minute]);
					assign("second", +match[groups.second]);
					assign("milli", +match[groups.milli]);
				}
				return parts;
			};

			utc.printISO = function(parts) {
				return utc.date(parts).toISOString();
			};

			utc.largest = function(parts) {
				for(var i = 0; i < all.length; i++) {
					if(all[i] in parts) {
						return all[i];
					}
				}
				return undefined;
			};

			utc.smallest = function(parts) {
				for(var i = all.length - 1; i >= 0; i--) {
					if(all[i] in parts) {
						return all[i];
					}
				}
				return undefined;
			};

			utc.chop = function(key, dt) {
				var result = {};
				for(var i = 0; i < all.length; i++) {
					var field = all[i];
					if(field in dt) {
						result[field] = dt[field];
					}
					if(field === key) {
						break;
					}
				}
				return result;
			};

			/**
			 * Carry-over any overflowing field to the next biggest.
			 *     {hour: 2, minute: 65}  ->  {hour: 3, minute: 5}
			 *
			 * Overflowing stops at the largest defined field, up to "day". For example:
			 *              {minute: 65}  ->  {minute: 65}
			 *     {hour: 0, minute: 65}  ->  {hour: 1, minute: 5}
			 *
			 * @param {Object} parts the UTC date parts.
			 * @returns {Object} new UTC date parts where any overflow has been carried over to the next biggest field.
			 */
			utc.carry = function(parts) {
				// CONSIDER: normalize and carry are pretty similar, except that carry is meant for durations (stops at "day").

				var result = {};
				if(parts.year !== undefined) result.year = parts.year;
				if(parts.month !== undefined) result.month = parts.month;

				var stop = utc.largest(parts);
				var day = parts.day;
				var hour = parts.hour;
				var minute = parts.minute;
				var second = parts.second;
				var milli = parts.milli;

				if(stop !== "milli") {
					if(milli >= 1000) {
						second = coalesce(second, 0) + Math.floor(milli / 1000);
						milli %= 1000;
					}
					if(stop !== "second") {
						if(second >= 60) {
							minute = coalesce(minute, 0) + Math.floor(second / 60);
							second %= 60;
						}
						if(stop !== "minute") {
							if(minute >= 60) {
								hour = coalesce(hour, 0) + Math.floor(minute / 60);
								minute %= 60;
							}
							if(stop !== "hour") {
								if(hour >= 24) {
									day = coalesce(day, 0) + Math.floor(hour / 24);
									hour %= 24;
								}
							}
						}
					}
				}

				if(day !== undefined) result.day = day;
				if(hour !== undefined) result.hour = hour;
				if(minute !== undefined) result.minute = minute;
				if(second !== undefined) result.second = second;
				if(milli !== undefined) result.milli = milli;

				return result;
			};

			/**
			 * @param parts the input datetime.
			 * @returns {Object} a datetime with all parts specified, filled in with zero when undefined.
			 */
			function fill(parts) {
				return {
					year: parts.year || 0,
					month: parts.month || 0,
					day: parts.day || 0,
					hour: parts.hour || 0,
					minute: parts.minute || 0,
					second: parts.second || 0,
					milli: parts.milli || 0,
				};
			}

			/**
			 * Accumulates the total duration of time into one part specified by key. Standard durations are used. Years and
			 * months are ignored because they convert to a variable number of days.
			 *
			 *       "hour", {hour: 2, minute: 65}  ->  {hour: 3}
			 *     "minute", {hour: 2, minute: 65}  ->  {minute: 185}
			 *     "second", {hour: 2, minute: 65}  ->  {second: 11100}
			 *
			 * @param {string} key the part to accumulate time into.
			 * @param {Object} parts the input duration.
			 * @returns {Object} datetime with one part, {key: }, where all the time has been accumulated into it.
			 */
			utc.accumulate = function(key, parts) {
				var smoothed = utc.carry(fill(parts));
				var accum = smoothed.day;
				if(key === "day") {
					return {
						day: accum
					};
				}
				accum = accum * 24 + smoothed.hour;
				if(key === "hour") {
					return {
						hour: accum
					};
				}
				accum = accum * 60 + smoothed.minute;
				if(key === "minute") {
					return {
						minute: accum
					};
				}
				accum = accum * 60 + smoothed.second;
				if(key === "second") {
					return {
						second: accum
					};
				}
				accum = accum * 1000 + smoothed.milli;
				if(key === "milli") {
					return {
						milli: accum
					};
				}
				var result = {};
				result[key] = undefined;
				return result;
			};

			/**
			 * @param start the starting UTC parts, inclusive
			 * @param end the ending UTC parts, inclusive
			 * @param delta the UTC parts
			 * @returns {Array} a range of UTC parts separated by delta
			 */
			utc.range = function(start, end, delta) {
				var results = [];
				for(var i = start; utc.compare(i, end) <= 0; i = utc.add(i, delta)) {
					results.push(utc.carry(i));
				}
				return results;
			};

			/**
			 * @param {Object} dt the datetime.
			 * @returns {number} the ordinal number of days from Jan 1 of the input year, starting at 1.
			 */
			utc.dayOfYear = function(dt) {
				var d1 = utc.date(dt),
					d0 = utc.date({
						year: d1.getUTCFullYear()
					});
				return Math.floor((d1 - d0) / (24 * 60 * 60 * 1000)) + 1; // No daylight savings in UTC.
			};

		}();

	}, {
		"underscore": 5
	}],
	62: [function(require, module, exports) {
		/*
		 * webglOverlay: webgl implementation of color overlay
		 *
		 * Copyright (c) 2016 Cameron Beccario
		 */
		"use strict";

		var m = module.exports = {};

		var ƒ = require("./func");

		m.checkCompatibility = function() {
			try {
				var canvas = ƒ.createCanvas();
				canvas.width = 1;
				canvas.height = 1;
				var gl = ƒ.getWebGL(canvas);
				if(!gl) {
					return "no WebGL context";
				}
				if(!gl.getExtension("OES_texture_float")) {
					return "no OES_texture_float";
				}
				var maxTexSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
				if(maxTexSize < 4096) {
					return "max texture size too small: " + maxTexSize;
				}
				if(!gl["getShaderPrecisionFormat"]) {
					return "webgl too old";
				}
				var highp = gl["getShaderPrecisionFormat"](gl.FRAGMENT_SHADER, gl.HIGH_FLOAT) || {};
				if((highp.precision || 0) < 23) {
					return "not enough float precision: " + highp.precision;
				}
				var vertexShader = gl.createShader(gl.VERTEX_SHADER);
				gl.shaderSource(vertexShader, "precision highp float; void main() { gl_Position = vec4(0.0); }");
				gl.compileShader(vertexShader);
				if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
					return "vertex shader: " + gl.getShaderInfoLog(vertexShader);
				}
				var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
				gl.shaderSource(fragmentShader, "precision highp float; void main() { gl_FragColor = vec4(0.0); }");
				gl.compileShader(fragmentShader);
				if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
					return "fragment shader: " + gl.getShaderInfoLog(fragmentShader);
				}
				var program = gl.createProgram();
				gl.attachShader(program, vertexShader);
				gl.attachShader(program, fragmentShader);
				gl.linkProgram(program);
				if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {
					return gl.getProgramInfoLog(program);
				}
				return gl.getError();
			} catch(e) {
				return e.toString();
			}
		};

	}, {
		"./func": 30
	}],
	63: [function(require, module, exports) {
		// shim for using process in browser
		var process = module.exports = {};

		// cached from whatever global is present so that test runners that stub it
		// don't break things.  But we need to wrap it in a try catch in case it is
		// wrapped in strict mode code which doesn't define any globals.  It's inside a
		// function because try/catches deoptimize in certain engines.

		var cachedSetTimeout;
		var cachedClearTimeout;

		(function() {
			try {
				cachedSetTimeout = setTimeout;
			} catch(e) {
				cachedSetTimeout = function() {
					throw new Error('setTimeout is not defined');
				}
			}
			try {
				cachedClearTimeout = clearTimeout;
			} catch(e) {
				cachedClearTimeout = function() {
					throw new Error('clearTimeout is not defined');
				}
			}
		}())

		function runTimeout(fun) {
			if(cachedSetTimeout === setTimeout) {
				return setTimeout(fun, 0);
			} else {
				return cachedSetTimeout.call(null, fun, 0);
			}
		}

		function runClearTimeout(marker) {
			if(cachedClearTimeout === clearTimeout) {
				clearTimeout(marker);
			} else {
				cachedClearTimeout.call(null, marker);
			}
		}
		var queue = [];
		var draining = false;
		var currentQueue;
		var queueIndex = -1;

		function cleanUpNextTick() {
			if(!draining || !currentQueue) {
				return;
			}
			draining = false;
			if(currentQueue.length) {
				queue = currentQueue.concat(queue);
			} else {
				queueIndex = -1;
			}
			if(queue.length) {
				drainQueue();
			}
		}

		function drainQueue() {
			if(draining) {
				return;
			}
			var timeout = runTimeout(cleanUpNextTick);
			draining = true;

			var len = queue.length;
			while(len) {
				currentQueue = queue;
				queue = [];
				while(++queueIndex < len) {
					if(currentQueue) {
						currentQueue[queueIndex].run();
					}
				}
				queueIndex = -1;
				len = queue.length;
			}
			currentQueue = null;
			draining = false;
			runClearTimeout(timeout);
		}

		process.nextTick = function(fun) {
			var args = new Array(arguments.length - 1);
			if(arguments.length > 1) {
				for(var i = 1; i < arguments.length; i++) {
					args[i - 1] = arguments[i];
				}
			}
			queue.push(new Item(fun, args));
			if(queue.length === 1 && !draining) {
				runTimeout(drainQueue);
			}
		};

		// v8 likes predictible objects
		function Item(fun, array) {
			this.fun = fun;
			this.array = array;
		}
		Item.prototype.run = function() {
			this.fun.apply(null, this.array);
		};
		process.title = 'browser';
		process.browser = true;
		process.env = {};
		process.argv = [];
		process.version = ''; // empty string to avoid regexp issues
		process.versions = {};

		function noop() {}

		process.on = noop;
		process.addListener = noop;
		process.once = noop;
		process.off = noop;
		process.removeListener = noop;
		process.removeAllListeners = noop;
		process.emit = noop;

		process.binding = function(name) {
			throw new Error('process.binding is not supported');
		};

		process.cwd = function() {
			return '/'
		};
		process.chdir = function(dir) {
			throw new Error('process.chdir is not supported');
		};
		process.umask = function() {
			return 0;
		};

	}, {}]
}, {}, [38]);